Flash 79 lines
// Freestanding C-ABI mem* providers for flibc-linked payloads.
//
// The flibc payloads build with `bundle_compiler_rt = false` (single R+X
// PT_LOAD, no compiler-rt baggage), so the C runtime helpers LLVM emits
// for certain patterns are not supplied. Most payloads dodge this: their
// loops do work per iteration and never match a libcall idiom. But once
// a program actually exercises flibc's `@memcpy`-backed builders (e.g.
// execvp's path-copy) or a bare `while (p[n] != 0)` length scan, LLVM's
// loop-idiom recognizer lowers them to `memcpy` / `strlen` calls and the
// link fails with "undefined symbol". fsh is the first such consumer
// (it calls execvp and tokenizes lines); echo / cat will be next.
//
// This mirrors the kernel's own hand-rolled providers in src/utilc.zig.
// The functions are named exactly `memcpy` / `memset` / `strlen`: LLVM's
// LoopIdiomRecognize pass refuses to rewrite a recognized idiom into a
// call to the very function being compiled, so the byte loops below are
// self-recursion-safe (the kernel's `memcpy` relies on the same guard).
//
// Opt-in like start.flash: a payload pulls these symbols in by importing
// this module as `flibc_mem` and forcing emission with
// `comptime { _ = @import("flibc_mem"); }`. It lives outside flibc's
// always-imported graph so payloads that don't need it (argv_echo,
// flibc_demo) are not bloated by kept-as-root exports.
/// memset(dst, c, n) — fill `n` bytes of `dst` with byte `c`. Byte
/// granular; the C ABI returns `dst`.
export fn memset(dst [*]mut u8, c i32, n_in u64) [*]mut u8 {
var n = n_in
var p = dst
const byte u8 = #truncate(#as(u32, #bitCast(c)))
while n != 0 {
p[0] = byte
p += 1
n -= 1
}
return dst
}
/// memcpy(dst, src, bytes) — copy `bytes` bytes from `src` to `dst`
/// (non-overlapping). Copies 8 bytes at a time when both operands are
/// 8-aligned, then drains the tail byte-wise. The C ABI returns `dst`.
export fn memcpy(dst *mut anyopaque, src *anyopaque, bytes u64) *mut anyopaque {
var d [*]mut u8 = #ptrCast(dst)
var s [*]u8 = #ptrCast(src)
var n = bytes
if #intFromPtr(d) % 8 == 0 && #intFromPtr(s) % 8 == 0 {
var d64 [*]mut u64 = #ptrCast(#alignCast(d))
var s64 [*]u64 = #ptrCast(#alignCast(s))
while n >= 8 {
d64[0] = s64[0]
d64 += 1
s64 += 1
n -= 8
}
d = #ptrCast(d64)
s = #ptrCast(s64)
}
while n > 0 {
d[0] = s[0]
d += 1
s += 1
n -= 1
}
return dst
}
/// strlen(s) — length of the NUL-terminated string at `s`, excluding the
/// terminator. The lone scan the idiom recognizer would otherwise route
/// to an external `strlen`; defining it here closes the loop.
export fn strlen(s [*:0]u8) u64 {
var n u64 = 0
while s[n] != 0 {
n += 1
}
return n
}