commit 7935e136ee6401e739de61fd9dbb8a1209ef9ab3
parent 78d4e8169776cc9364c5d4ea469bcde626352111
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 20 May 2026 07:31:38 -0700
rt: implement freestanding header routines
Diffstat:
9 files changed, 995 insertions(+), 44 deletions(-)
diff --git a/rt/Makefile b/rt/Makefile
@@ -134,9 +134,15 @@ RT_arm-eabi-thumb1_AEABI = thumb1
# ---------- sources and flags derived from features --------------------------
RT_BASE_SRCS = \
+ rt/lib/assert/assert.c \
rt/lib/int/int.c \
+ rt/lib/int/si_div.c \
rt/lib/fp/fp.c \
rt/lib/mem/mem.c \
+ rt/lib/string/string.c \
+ rt/lib/stdlib/stdlib.c \
+ rt/lib/stdlib/qsort.c \
+ rt/lib/stdio/printf.c \
rt/lib/atomic/atomic_freestanding.c \
rt/lib/cache/clear_cache.c \
rt/lib/cfree/ifunc_init.c
diff --git a/rt/lib/assert/assert.c b/rt/lib/assert/assert.c
@@ -0,0 +1,16 @@
+//===-- assert.c - cfree freestanding assert failure ----------------------===//
+//
+// SPDX-License-Identifier: 0BSD
+//===----------------------------------------------------------------------===//
+
+__attribute__((weak)) void __cfree_assert_fail(const char* expr,
+ const char* file, int line,
+ const char* func) {
+ (void)expr;
+ (void)file;
+ (void)line;
+ (void)func;
+ __builtin_trap();
+ for (;;) {
+ }
+}
diff --git a/rt/lib/fp_tf/fp_tf.c b/rt/lib/fp_tf/fp_tf.c
@@ -13,50 +13,6 @@
#include "tf_supplement.h"
#endif
-// cfree's freestanding aarch64/RV64 backend may lower 32-bit divisions in
-// this runtime TU back into compiler-rt spellings. Keep these leaf helpers in
-// the same archive member as the quad helpers so demand-loaded links resolve
-// the complete member closure.
-COMPILER_RT_ABI su_int __udivsi3(su_int n, su_int d) {
- const unsigned bits = sizeof(su_int) * CHAR_BIT;
- su_int q = 0;
- su_int r = 0;
- for (unsigned i = bits; i > 0; --i) {
- r = (su_int)((r << 1) | ((n >> (i - 1u)) & 1u));
- if (r >= d) {
- r = (su_int)(r - d);
- q |= (su_int)1u << (i - 1u);
- }
- }
- return q;
-}
-
-COMPILER_RT_ABI su_int __udivmodsi4(su_int n, su_int d, su_int* rem) {
- const unsigned bits = sizeof(su_int) * CHAR_BIT;
- su_int q = 0;
- su_int r = 0;
- for (unsigned i = bits; i > 0; --i) {
- r = (su_int)((r << 1) | ((n >> (i - 1u)) & 1u));
- if (r >= d) {
- r = (su_int)(r - d);
- q |= (su_int)1u << (i - 1u);
- }
- }
- if (rem)
- *rem = r;
- return q;
-}
-
-COMPILER_RT_ABI si_int __divsi3(si_int a, si_int b) {
- su_int sa = (su_int)(a >> 31);
- su_int sb = (su_int)(b >> 31);
- su_int ua = ((su_int)a ^ sa) - sa;
- su_int ub = ((su_int)b ^ sb) - sb;
- su_int sign = sa ^ sb;
- su_int q = __udivsi3(ua, ub);
- return (si_int)((q ^ sign) - sign);
-}
-
// ---- addtf3.c ----
#define QUAD_PRECISION
#include "fp_add_impl.inc"
diff --git a/rt/lib/int/si_div.c b/rt/lib/int/si_div.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// 32-bit integer division helpers required by compiler-rt routines even on
+// LP64 targets where normal C int division is otherwise a single instruction.
+
+#include "int_lib.h"
+
+COMPILER_RT_ABI su_int __udivsi3(su_int n, su_int d) {
+ const unsigned bits = sizeof(su_int) * CHAR_BIT;
+ su_int q = 0;
+ su_int r = 0;
+ for (unsigned i = bits; i > 0; --i) {
+ r = (su_int)((r << 1) | ((n >> (i - 1u)) & 1u));
+ if (r >= d) {
+ r = (su_int)(r - d);
+ q |= (su_int)1u << (i - 1u);
+ }
+ }
+ return q;
+}
+
+COMPILER_RT_ABI su_int __udivmodsi4(su_int n, su_int d, su_int* rem) {
+ const unsigned bits = sizeof(su_int) * CHAR_BIT;
+ su_int q = 0;
+ su_int r = 0;
+ for (unsigned i = bits; i > 0; --i) {
+ r = (su_int)((r << 1) | ((n >> (i - 1u)) & 1u));
+ if (r >= d) {
+ r = (su_int)(r - d);
+ q |= (su_int)1u << (i - 1u);
+ }
+ }
+ if (rem) *rem = r;
+ return q;
+}
+
+COMPILER_RT_ABI si_int __divsi3(si_int a, si_int b) {
+ su_int sa = (su_int)(a >> 31);
+ su_int sb = (su_int)(b >> 31);
+ su_int ua = ((su_int)a ^ sa) - sa;
+ su_int ub = ((su_int)b ^ sb) - sb;
+ su_int sign = sa ^ sb;
+ su_int q = __udivsi3(ua, ub);
+ return (si_int)((q ^ sign) - sign);
+}
diff --git a/rt/lib/stdio/printf.c b/rt/lib/stdio/printf.c
@@ -0,0 +1,243 @@
+//===-- printf.c - cfree freestanding printf routines ---------------------===//
+//
+// Based on the mpaland/printf MIT-licensed formatter, imported for cfree's
+// freestanding runtime and reduced to the top-level <stdio.h> declarations.
+// 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 struct {
+ char* buf;
+ size_t cap;
+ size_t len;
+} CfreePrintfOut;
+
+static void cfree_out_ch(CfreePrintfOut* out, char ch) {
+ if (out->cap && out->len + 1U < out->cap) out->buf[out->len] = ch;
+ out->len++;
+}
+
+static void cfree_out_repeat(CfreePrintfOut* out, char ch, unsigned count) {
+ while (count-- > 0) cfree_out_ch(out, ch);
+}
+
+static unsigned cfree_strlen_prec(const char* s, int has_prec,
+ unsigned prec) {
+ unsigned n = 0;
+ if (!s) s = "(null)";
+ while (s[n] != '\0' && (!has_prec || n < prec)) n++;
+ return n;
+}
+
+static void cfree_out_string(CfreePrintfOut* out, const char* s, int left,
+ unsigned width, int has_prec, unsigned prec) {
+ unsigned len = cfree_strlen_prec(s, has_prec, prec);
+ if (!s) s = "(null)";
+ if (!left && width > len) cfree_out_repeat(out, ' ', width - len);
+ for (unsigned i = 0; i < len; i++) cfree_out_ch(out, s[i]);
+ if (left && width > len) cfree_out_repeat(out, ' ', width - len);
+}
+
+static void cfree_out_uint(CfreePrintfOut* out, unsigned long long value,
+ unsigned base, int upper, int neg, int left,
+ int zero, unsigned width) {
+ char tmp[64];
+ unsigned len = 0;
+ unsigned digits_len;
+ const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef";
+ unsigned prefix = neg ? 1U : 0U;
+ char pad = (zero && !left) ? '0' : ' ';
+
+ do {
+ tmp[len++] = digits[value % base];
+ value /= base;
+ } while (value != 0);
+ digits_len = len;
+
+ if (!left && pad == ' ' && width > len + prefix) {
+ cfree_out_repeat(out, ' ', width - len - prefix);
+ }
+ if (neg) cfree_out_ch(out, '-');
+ if (!left && pad == '0' && width > len + prefix) {
+ cfree_out_repeat(out, '0', width - len - prefix);
+ }
+ while (len > 0) cfree_out_ch(out, tmp[--len]);
+ if (left && width > digits_len + prefix) {
+ cfree_out_repeat(out, ' ', width - digits_len - prefix);
+ }
+}
+
+static int cfree_is_digit(char ch) { return ch >= '0' && ch <= '9'; }
+
+static unsigned cfree_parse_uint(const char** p) {
+ unsigned value = 0;
+ while (cfree_is_digit(**p)) {
+ value = value * 10U + (unsigned)(*(*p)++ - '0');
+ }
+ 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;
+
+ while (*fmt) {
+ int left = 0;
+ int zero = 0;
+ int long_mod = 0;
+ int long_long_mod = 0;
+ int has_prec = 0;
+ unsigned width = 0;
+ unsigned prec = 0;
+
+ if (*fmt != '%') {
+ cfree_out_ch(&out, *fmt++);
+ continue;
+ }
+ fmt++;
+ if (*fmt == '%') {
+ cfree_out_ch(&out, *fmt++);
+ continue;
+ }
+
+ for (;;) {
+ if (*fmt == '-') {
+ left = 1;
+ fmt++;
+ } else if (*fmt == '0') {
+ zero = 1;
+ fmt++;
+ } else {
+ break;
+ }
+ }
+ if (cfree_is_digit(*fmt)) width = cfree_parse_uint(&fmt);
+ if (*fmt == '.') {
+ fmt++;
+ has_prec = 1;
+ prec = cfree_parse_uint(&fmt);
+ }
+ if (*fmt == 'l') {
+ fmt++;
+ if (*fmt == 'l') {
+ fmt++;
+ long_long_mod = 1;
+ } else {
+ long_mod = 1;
+ }
+ } else if (*fmt == 'h') {
+ fmt++;
+ if (*fmt == 'h') fmt++;
+ }
+
+ switch (*fmt) {
+ case 'd':
+ case 'i': {
+ long long v;
+ unsigned long long mag;
+ if (long_long_mod) {
+ v = va_arg(ap, long long);
+ } else if (long_mod) {
+ v = va_arg(ap, long);
+ } else {
+ 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);
+ break;
+ }
+ case 'u':
+ case 'x':
+ case 'X':
+ case 'o': {
+ unsigned long long v;
+ unsigned base = *fmt == 'o' ? 8U : ((*fmt == 'u') ? 10U : 16U);
+ if (long_long_mod) {
+ v = va_arg(ap, unsigned long long);
+ } else if (long_mod) {
+ v = va_arg(ap, unsigned long);
+ } else {
+ v = va_arg(ap, unsigned int);
+ }
+ 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);
+ break;
+ }
+ case 'c':
+ 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,
+ prec);
+ break;
+ default:
+ if (*fmt) cfree_out_ch(&out, *fmt);
+ break;
+ }
+ if (*fmt) fmt++;
+ }
+
+ if (out.cap) {
+ size_t pos = out.len < out.cap ? out.len : out.cap - 1U;
+ out.buf[pos] = '\0';
+ }
+ return (int)out.len;
+}
+
+int vsnprintf(char* s, size_t n, const char* fmt, va_list ap) {
+ va_list copy;
+ int rc;
+ va_copy(copy, ap);
+ rc = cfree_vsnprintf_impl(s, n, fmt, copy);
+ va_end(copy);
+ return rc;
+}
+
+int snprintf(char* s, size_t n, const char* fmt, ...) {
+ va_list ap;
+ int rc;
+ va_start(ap, fmt);
+ rc = vsnprintf(s, n, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+int vsprintf(char* s, const char* fmt, va_list ap) {
+ return vsnprintf(s, (size_t)-1, fmt, ap);
+}
+
+int sprintf(char* s, const char* fmt, ...) {
+ va_list ap;
+ int rc;
+ va_start(ap, fmt);
+ rc = vsprintf(s, fmt, ap);
+ va_end(ap);
+ return rc;
+}
diff --git a/rt/lib/stdlib/qsort.c b/rt/lib/stdlib/qsort.c
@@ -0,0 +1,234 @@
+/* Copyright (C) 2011 by Lynn Ochs
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* Minor changes by Rich Felker for integration in musl, 2011-04-27. */
+
+/* Smoothsort, an adaptive variant of Heapsort. Memory usage: O(1).
+ Run time: Worst case O(n log n), close to O(n) in the mostly-sorted case. */
+
+#include <stdint.h>
+#include <stddef.h>
+
+void *memcpy(void *dest, const void *src, size_t n);
+
+typedef int (*cmpfun)(const void *, const void *);
+
+static int ntz(size_t x)
+{
+ int n = 0;
+ while ((x & 1) == 0) {
+ x >>= 1;
+ n++;
+ }
+ return n;
+}
+
+/* returns index of first bit set, excluding the low bit assumed to always
+ * be set, starting from low bit of p[0] up through high bit of p[1] */
+static inline int pntz(size_t p[2]) {
+ if (p[0] != 1) return ntz(p[0] - 1);
+ if (p[1]) return 8*sizeof(size_t) + ntz(p[1]);
+ return 0;
+}
+
+static void cycle(size_t width, unsigned char* ar[], int n)
+{
+ unsigned char tmp[256];
+ size_t l;
+ int i;
+
+ if(n < 2) {
+ return;
+ }
+
+ ar[n] = tmp;
+ while(width) {
+ l = sizeof(tmp) < width ? sizeof(tmp) : width;
+ memcpy(ar[n], ar[0], l);
+ for(i = 0; i < n; i++) {
+ memcpy(ar[i], ar[i + 1], l);
+ ar[i] += l;
+ }
+ width -= l;
+ }
+}
+
+/* shl() and shr() need n > 0 */
+static inline void shl(size_t p[2], int n)
+{
+ if(n >= 8 * sizeof(size_t)) {
+ n -= 8 * sizeof(size_t);
+ p[1] = p[0];
+ p[0] = 0;
+ if (!n) return;
+ }
+ p[1] <<= n;
+ p[1] |= p[0] >> (sizeof(size_t) * 8 - n);
+ p[0] <<= n;
+}
+
+static inline void shr(size_t p[2], int n)
+{
+ if(n >= 8 * sizeof(size_t)) {
+ n -= 8 * sizeof(size_t);
+ p[0] = p[1];
+ p[1] = 0;
+ if (!n) return;
+ }
+ p[0] >>= n;
+ p[0] |= p[1] << (sizeof(size_t) * 8 - n);
+ p[1] >>= n;
+}
+
+/* power-of-two length for working array so that we can mask indices and
+ * not depend on any invariant of the algorithm for spatial memory safety.
+ * the original size was just 14*sizeof(size_t)+1 */
+#define AR_LEN (16 * sizeof(size_t))
+#define AR_MASK (AR_LEN - 1)
+
+static void sift(unsigned char *head, size_t width, cmpfun cmp, int pshift, size_t lp[])
+{
+ unsigned char *rt, *lf;
+ unsigned char *ar[AR_LEN];
+ int i = 1;
+
+ ar[0] = head;
+ while(pshift > 1) {
+ rt = head - width;
+ lf = head - width - lp[pshift - 2];
+
+ if(cmp(ar[0], lf) >= 0 && cmp(ar[0], rt) >= 0) {
+ break;
+ }
+ if(cmp(lf, rt) >= 0) {
+ ar[i++ & AR_MASK] = lf;
+ head = lf;
+ pshift -= 1;
+ } else {
+ ar[i++ & AR_MASK] = rt;
+ head = rt;
+ pshift -= 2;
+ }
+ }
+ cycle(width, ar, i & AR_MASK);
+}
+
+static void trinkle(unsigned char *head, size_t width, cmpfun cmp, size_t pp[2], int pshift, int trusty, size_t lp[])
+{
+ unsigned char *stepson,
+ *rt, *lf;
+ size_t p[2];
+ unsigned char *ar[AR_LEN];
+ int i = 1;
+ int trail;
+
+ p[0] = pp[0];
+ p[1] = pp[1];
+
+ ar[0] = head;
+ while(p[0] != 1 || p[1] != 0) {
+ stepson = head - lp[pshift];
+ if(cmp(stepson, ar[0]) <= 0) {
+ break;
+ }
+ if(!trusty && pshift > 1) {
+ rt = head - width;
+ lf = head - width - lp[pshift - 2];
+ if(cmp(rt, stepson) >= 0 || cmp(lf, stepson) >= 0) {
+ break;
+ }
+ }
+
+ ar[i++ & AR_MASK] = stepson;
+ head = stepson;
+ trail = pntz(p);
+ shr(p, trail);
+ pshift += trail;
+ trusty = 0;
+ }
+ if(!trusty) {
+ cycle(width, ar, i & AR_MASK);
+ sift(head, width, cmp, pshift, lp);
+ }
+}
+
+void qsort(void *base, size_t nel, size_t width, cmpfun cmp)
+{
+ size_t lp[12*sizeof(size_t)];
+ size_t i, size = width * nel;
+ unsigned char *head, *high;
+ size_t p[2] = {1, 0};
+ int pshift = 1;
+ int trail;
+
+ if (!size) return;
+
+ head = base;
+ high = head + size - width;
+
+ /* Precompute Leonardo numbers, scaled by element width */
+ for(lp[0]=lp[1]=width, i=2; (lp[i]=lp[i-2]+lp[i-1]+width) < size; i++);
+
+ while(head < high) {
+ if((p[0] & 3) == 3) {
+ sift(head, width, cmp, pshift, lp);
+ shr(p, 2);
+ pshift += 2;
+ } else {
+ if(lp[pshift - 1] >= high - head) {
+ trinkle(head, width, cmp, p, pshift, 0, lp);
+ } else {
+ sift(head, width, cmp, pshift, lp);
+ }
+
+ if(pshift == 1) {
+ shl(p, 1);
+ pshift = 0;
+ } else {
+ shl(p, pshift - 1);
+ pshift = 1;
+ }
+ }
+
+ p[0] |= 1;
+ head += width;
+ }
+
+ trinkle(head, width, cmp, p, pshift, 0, lp);
+
+ while(pshift != 1 || p[0] != 1 || p[1] != 0) {
+ if(pshift <= 1) {
+ trail = pntz(p);
+ shr(p, trail);
+ pshift += trail;
+ } else {
+ shl(p, 2);
+ pshift -= 2;
+ p[0] ^= 7;
+ shr(p, 1);
+ trinkle(head - lp[pshift] - width, width, cmp, p, pshift + 1, 1, lp);
+ shl(p, 1);
+ p[0] |= 1;
+ trinkle(head - width, width, cmp, p, pshift, 1, lp);
+ }
+ head -= width;
+ }
+}
diff --git a/rt/lib/stdlib/stdlib.c b/rt/lib/stdlib/stdlib.c
@@ -0,0 +1,218 @@
+//===-- stdlib.c - cfree freestanding stdlib primitives -------------------===//
+//
+// SPDX-License-Identifier: 0BSD
+//===----------------------------------------------------------------------===//
+
+#include <stddef.h>
+
+typedef struct {
+ int quot;
+ int rem;
+} div_t;
+
+typedef struct {
+ long quot;
+ long rem;
+} ldiv_t;
+
+typedef struct {
+ long long quot;
+ long long rem;
+} lldiv_t;
+
+static int cfree_isspace(char c) {
+ return c == ' ' || (c >= '\t' && c <= '\r');
+}
+
+static int cfree_digit_value(char c) {
+ if (c >= '0' && c <= '9') return c - '0';
+ if (c >= 'a' && c <= 'z') return c - 'a' + 10;
+ if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
+ return -1;
+}
+
+static int cfree_match_ci(const char* s, const char* word) {
+ while (*word != '\0') {
+ char a = *s++;
+ char b = *word++;
+ if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a');
+ if (a != b) return 0;
+ }
+ return 1;
+}
+
+unsigned long long strtoull(const char* nptr, char** endptr, int base) {
+ const char* s = nptr;
+ int neg = 0;
+ unsigned long long value = 0;
+ int any = 0;
+
+ while (cfree_isspace(*s)) s++;
+ if (*s == '+' || *s == '-') neg = *s++ == '-';
+
+ if ((base == 0 || base == 16) && s[0] == '0' &&
+ (s[1] == 'x' || s[1] == 'X')) {
+ int d = cfree_digit_value(s[2]);
+ if (d >= 0 && d < 16) {
+ s += 2;
+ base = 16;
+ }
+ }
+ if (base == 0) base = (*s == '0') ? 8 : 10;
+ if (base < 2 || base > 36) {
+ if (endptr) *endptr = (char*)nptr;
+ return 0;
+ }
+
+ for (;;) {
+ int d = cfree_digit_value(*s);
+ if (d < 0 || d >= base) break;
+ value = value * (unsigned)base + (unsigned)d;
+ s++;
+ any = 1;
+ }
+
+ if (endptr) *endptr = (char*)(any ? s : nptr);
+ return neg ? 0 - value : value;
+}
+
+unsigned long strtoul(const char* nptr, char** endptr, int base) {
+ return (unsigned long)strtoull(nptr, endptr, base);
+}
+
+long long strtoll(const char* nptr, char** endptr, int base) {
+ return (long long)strtoull(nptr, endptr, base);
+}
+
+long strtol(const char* nptr, char** endptr, int base) {
+ return (long)strtoull(nptr, endptr, base);
+}
+
+long long atoll(const char* nptr) { return strtoll(nptr, NULL, 10); }
+long atol(const char* nptr) { return strtol(nptr, NULL, 10); }
+int atoi(const char* nptr) { return (int)strtol(nptr, NULL, 10); }
+
+static long double cfree_strtold_impl(const char* nptr, char** endptr) {
+ const char* s = nptr;
+ int neg = 0;
+ int any = 0;
+ long double value = 0.0L;
+
+ while (cfree_isspace(*s)) s++;
+ if (*s == '+' || *s == '-') neg = *s++ == '-';
+
+ if (cfree_match_ci(s, "infinity")) {
+ if (endptr) *endptr = (char*)(s + 8);
+ return neg ? -(1.0L / 0.0L) : (1.0L / 0.0L);
+ }
+ if (cfree_match_ci(s, "inf")) {
+ if (endptr) *endptr = (char*)(s + 3);
+ return neg ? -(1.0L / 0.0L) : (1.0L / 0.0L);
+ }
+ if (cfree_match_ci(s, "nan")) {
+ if (endptr) *endptr = (char*)(s + 3);
+ return 0.0L / 0.0L;
+ }
+
+ while (*s >= '0' && *s <= '9') {
+ value = value * 10.0L + (long double)(*s - '0');
+ s++;
+ any = 1;
+ }
+
+ if (*s == '.') {
+ long double place = 0.1L;
+ s++;
+ while (*s >= '0' && *s <= '9') {
+ value += (long double)(*s - '0') * place;
+ place *= 0.1L;
+ s++;
+ any = 1;
+ }
+ }
+
+ if (!any) {
+ if (endptr) *endptr = (char*)nptr;
+ return 0.0L;
+ }
+
+ if (*s == 'e' || *s == 'E') {
+ const char* exp_start = s;
+ int exp_neg = 0;
+ int exp_any = 0;
+ int exp = 0;
+ s++;
+ if (*s == '+' || *s == '-') exp_neg = *s++ == '-';
+ while (*s >= '0' && *s <= '9') {
+ exp = exp * 10 + (*s - '0');
+ s++;
+ exp_any = 1;
+ }
+ if (exp_any) {
+ while (exp-- > 0) value = exp_neg ? value / 10.0L : value * 10.0L;
+ } else {
+ s = exp_start;
+ }
+ }
+
+ if (endptr) *endptr = (char*)s;
+ return neg ? -value : value;
+}
+
+long double strtold(const char* nptr, char** endptr) {
+ return cfree_strtold_impl(nptr, endptr);
+}
+
+double strtod(const char* nptr, char** endptr) {
+ return (double)cfree_strtold_impl(nptr, endptr);
+}
+
+float strtof(const char* nptr, char** endptr) {
+ return (float)cfree_strtold_impl(nptr, endptr);
+}
+
+int abs(int j) { return j < 0 ? -j : j; }
+long labs(long j) { return j < 0 ? -j : j; }
+long long llabs(long long j) { return j < 0 ? -j : j; }
+
+div_t div(int numer, int denom) {
+ div_t r;
+ r.quot = numer / denom;
+ r.rem = numer % denom;
+ return r;
+}
+
+ldiv_t ldiv(long numer, long denom) {
+ ldiv_t r;
+ r.quot = numer / denom;
+ r.rem = numer % denom;
+ return r;
+}
+
+lldiv_t lldiv(long long numer, long long denom) {
+ lldiv_t r;
+ r.quot = numer / denom;
+ r.rem = numer % denom;
+ return r;
+}
+
+void* bsearch(const void* key, const void* base, size_t nmemb, size_t size,
+ int (*compar)(const void*, const void*)) {
+ const unsigned char* b = (const unsigned char*)base;
+ size_t low = 0;
+ size_t high = nmemb;
+
+ while (low < high) {
+ size_t mid = low + (high - low) / 2;
+ const void* elem = b + mid * size;
+ int cmp = compar(key, elem);
+ if (cmp < 0) {
+ high = mid;
+ } else if (cmp > 0) {
+ low = mid + 1;
+ } else {
+ return (void*)elem;
+ }
+ }
+ return NULL;
+}
diff --git a/rt/lib/string/string.c b/rt/lib/string/string.c
@@ -0,0 +1,109 @@
+//===-- string.c - cfree freestanding string primitives -------------------===//
+//
+// SPDX-License-Identifier: 0BSD
+//===----------------------------------------------------------------------===//
+
+#include <stddef.h>
+
+__attribute__((weak)) void* memchr(const void* s, int c, size_t n) {
+ const unsigned char* p = (const unsigned char*)s;
+ unsigned char v = (unsigned char)c;
+ for (size_t i = 0; i < n; i++) {
+ if (p[i] == v) return (void*)(p + i);
+ }
+ return NULL;
+}
+
+__attribute__((weak)) size_t strlen(const char* s) {
+ size_t n = 0;
+ while (s[n] != '\0') n++;
+ return n;
+}
+
+__attribute__((weak)) size_t strnlen(const char* s, size_t maxlen) {
+ size_t n = 0;
+ while (n < maxlen && s[n] != '\0') n++;
+ return n;
+}
+
+__attribute__((weak)) char* strcpy(char* dest, const char* src) {
+ char* d = dest;
+ while ((*d++ = *src++) != '\0') {
+ }
+ return dest;
+}
+
+__attribute__((weak)) char* strncpy(char* dest, const char* src, size_t n) {
+ size_t i = 0;
+ for (; i < n && src[i] != '\0'; i++) dest[i] = src[i];
+ for (; i < n; i++) dest[i] = '\0';
+ return dest;
+}
+
+__attribute__((weak)) char* strcat(char* dest, const char* src) {
+ strcpy(dest + strlen(dest), src);
+ return dest;
+}
+
+__attribute__((weak)) char* strncat(char* dest, const char* src, size_t n) {
+ char* d = dest + strlen(dest);
+ size_t i = 0;
+ while (i < n && src[i] != '\0') {
+ d[i] = src[i];
+ i++;
+ }
+ d[i] = '\0';
+ return dest;
+}
+
+__attribute__((weak)) int strcmp(const char* s1, const char* s2) {
+ const unsigned char* a = (const unsigned char*)s1;
+ const unsigned char* b = (const unsigned char*)s2;
+ while (*a != '\0' && *a == *b) {
+ a++;
+ b++;
+ }
+ return (int)*a - (int)*b;
+}
+
+__attribute__((weak)) int strncmp(const char* s1, const char* s2, size_t n) {
+ const unsigned char* a = (const unsigned char*)s1;
+ const unsigned char* b = (const unsigned char*)s2;
+ for (size_t i = 0; i < n; i++) {
+ if (a[i] != b[i] || a[i] == '\0') return (int)a[i] - (int)b[i];
+ }
+ return 0;
+}
+
+__attribute__((weak)) char* strchr(const char* s, int c) {
+ char v = (char)c;
+ for (;;) {
+ if (*s == v) return (char*)s;
+ if (*s == '\0') return NULL;
+ s++;
+ }
+}
+
+__attribute__((weak)) char* strrchr(const char* s, int c) {
+ char v = (char)c;
+ const char* last = NULL;
+ for (;;) {
+ if (*s == v) last = s;
+ if (*s == '\0') return (char*)last;
+ s++;
+ }
+}
+
+__attribute__((weak)) char* strstr(const char* haystack, const char* needle) {
+ if (*needle == '\0') return (char*)haystack;
+ for (; *haystack != '\0'; haystack++) {
+ const char* h = haystack;
+ const char* n = needle;
+ while (*n != '\0' && *h == *n) {
+ h++;
+ n++;
+ }
+ if (*n == '\0') return (char*)haystack;
+ }
+ return NULL;
+}
diff --git a/test/rt/cases/freestanding_lib.c b/test/rt/cases/freestanding_lib.c
@@ -0,0 +1,124 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int cmp_int(const void* a, const void* b) {
+ int lhs = *(const int*)a;
+ int rhs = *(const int*)b;
+ return (lhs > rhs) - (lhs < rhs);
+}
+
+static int check_vsnprintf(char* out, size_t n, const char* fmt, ...) {
+ va_list ap;
+ int rc;
+ va_start(ap, fmt);
+ rc = vsnprintf(out, n, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+static int check_vsprintf(char* out, const char* fmt, ...) {
+ va_list ap;
+ int rc;
+ va_start(ap, fmt);
+ rc = vsprintf(out, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+static int strings_ok(void) {
+ char buf[32];
+ char small[8];
+ const char* text = "alpha beta";
+
+ memset(buf, 0, sizeof(buf));
+ memcpy(buf, "abc", 4);
+ memmove(buf + 1, buf, 3);
+ if (strcmp(buf, "aabc") != 0) return 0;
+ if (memcmp(buf, "aabc", 4) != 0) return 0;
+ if (*(char*)memchr(buf, 'b', 4) != 'b') return 0;
+
+ strcpy(buf, "alpha");
+ strcat(buf, " ");
+ strncat(buf, "beta!", 4);
+ if (strcmp(buf, text) != 0) return 0;
+ if (strncmp(buf, "alpha", 5) != 0) return 0;
+ if (strlen(buf) != 10) return 0;
+ if (strnlen(buf, 4) != 4) return 0;
+ if (strchr(buf, ' ') != buf + 5) return 0;
+ if (strrchr(buf, 'a') != buf + 9) return 0;
+ if (strstr(buf, "ha be") != buf + 3) return 0;
+
+ strncpy(small, "xy", sizeof(small));
+ if (small[0] != 'x' || small[1] != 'y' || small[2] != '\0') return 0;
+ return 1;
+}
+
+static int stdlib_ok(void) {
+ char* end;
+ int values[6] = {5, 1, 4, 2, 3, 0};
+ int key = 4;
+ int* found;
+ div_t d;
+ ldiv_t ld;
+ lldiv_t lld;
+
+ if (atoi(" -42x") != -42) return 0;
+ if (atol("123") != 123L) return 0;
+ if (atoll("-456") != -456LL) return 0;
+ if (strtol("0x2a!", &end, 0) != 42 || *end != '!') return 0;
+ if (strtoll("-077", &end, 0) != -63 || *end != '\0') return 0;
+ if (strtoul("101", &end, 2) != 5UL || *end != '\0') return 0;
+ if (strtoull("ff", &end, 16) != 255ULL || *end != '\0') return 0;
+ if ((int)(strtod("12.5e1z", &end) + 0.5) != 125 || *end != 'z') return 0;
+ if ((int)(strtof("4.25", &end) * 4.0f) != 17 || *end != '\0') return 0;
+ if ((int)strtold("9.0", &end) != 9 || *end != '\0') return 0;
+
+ if (abs(-7) != 7 || labs(-8L) != 8L || llabs(-9LL) != 9LL) return 0;
+ d = div(17, 5);
+ ld = ldiv(17L, 5L);
+ lld = lldiv(17LL, 5LL);
+ if (d.quot != 3 || d.rem != 2) return 0;
+ if (ld.quot != 3 || ld.rem != 2) return 0;
+ if (lld.quot != 3 || lld.rem != 2) return 0;
+
+ qsort(values, 6, sizeof(values[0]), cmp_int);
+ for (int i = 0; i < 6; i++) {
+ if (values[i] != i) return 0;
+ }
+ found = bsearch(&key, values, 6, sizeof(values[0]), cmp_int);
+ if (found == NULL || *found != key) return 0;
+ return 1;
+}
+
+static int stdio_ok(void) {
+ char buf[64];
+ 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;
+
+ rc = snprintf(buf, 6, "abcdef");
+ if (rc != 6 || strcmp(buf, "abcde") != 0) return 0;
+
+ rc = sprintf(buf, "%u/%ld/%p", 12U, 34L, (void*)0x1234);
+ if (rc <= 0 || strstr(buf, "12/34/") != buf) return 0;
+
+ rc = check_vsnprintf(buf, sizeof(buf), "%s %02d", "v", 3);
+ if (rc != 4 || strcmp(buf, "v 03") != 0) return 0;
+
+ rc = check_vsprintf(buf, "%lld", 123456789LL);
+ if (rc != 9 || strcmp(buf, "123456789") != 0) return 0;
+ return 1;
+}
+
+int test_main(void) {
+ assert(1);
+ if (!strings_ok()) return 1;
+ if (!stdlib_ok()) return 2;
+ if (!stdio_ok()) return 3;
+ return 42;
+}