user: implement mlibc as the libc, finally.

It's finally done..

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
This commit is contained in:
kaguya
2026-05-02 03:31:49 -04:00
parent 2fa39ad85a
commit 9a9b91c940
2387 changed files with 152741 additions and 315 deletions
@@ -0,0 +1,209 @@
#include <string.h>
#include <bits/ensure.h>
#include <frg/eternal.hpp>
#include <mlibc/allocator.hpp>
#include <mlibc/internal-sysdeps.hpp>
#include <internal-config.h>
#if !MLIBC_DEBUG_ALLOCATOR
// --------------------------------------------------------
// Globals
// --------------------------------------------------------
MemoryAllocator &getAllocator() {
// use frg::eternal to prevent a call to __cxa_atexit().
// this is necessary because __cxa_atexit() call this function.
static frg::eternal<VirtualAllocator> virtualAllocator;
static frg::eternal<MemoryPool> heap{virtualAllocator.get()};
static frg::eternal<MemoryAllocator> singleton{&heap.get()};
return singleton.get();
}
// --------------------------------------------------------
// VirtualAllocator
// --------------------------------------------------------
uintptr_t VirtualAllocator::map(size_t length) {
void *ptr;
__ensure(!mlibc::sys_anon_allocate(length, &ptr));
return (uintptr_t)ptr;
}
void VirtualAllocator::unmap(uintptr_t address, size_t length) {
__ensure(!mlibc::sys_anon_free((void *)address, length));
}
#else
namespace {
struct AllocatorMeta {
size_t allocatedSize;
size_t pagesSize;
frg::array<uint64_t, 4> magic;
};
constexpr frg::array<uint64_t, 4> allocatorMagic {
0x6d4bbb9f3446e83f, 0x25e213a7a7f9f954,
0x1a3c667586538bef, 0x994f34ff71c090bc
};
} // namespace anonymous
// Turn vm_unmap calls in free into vm_map(..., PROT_NONE, ...) calls to prevent
// those addresses from being reused. This is useful for detecting situations like this:
// 1. Allocate object X at address Y
// 2. Do some computation using object X
// 3. Free object X at address Y
// 4. Allocate object Z at address W, and it so happens that W == Y
// 5. Try to use object X, but the memory which was backing it now contains object Z
constexpr bool neverReleaseVa = false;
constexpr bool logAllocations = false;
// Area before the returned allocated block (which exists due to us offseting
// the block to be as close to the edge of a page).
constexpr uint8_t offsetAreaValue = 'A';
// Area which we return a pointer to in allocate and reallocate.
constexpr uint8_t allocatedAreaValue = 'B';
// Area after the allocated block, which exists due to the alignment constraints.
constexpr uint8_t alignmentAreaValue = 'C';
// Remaining area within the metadata page after the metadata.
constexpr uint8_t metaAreaValue = 'D';
// Alignment of the returned memory.
// TODO(qookie): Eventually accept alignment as an argument of allocate.
constexpr size_t pointerAlignment = 16;
// TODO(qookie): Support this. Perhaps by overallocating by 2x and then picking
// an offset that guarantees the desired alignment.
static_assert(pointerAlignment <= 4096, "Pointer aligment of more than 4096 bytes is unsupported");
static_assert(!(pointerAlignment & (pointerAlignment - 1)),
"Pointer aligment must be a power of 2");
constexpr size_t pageSize = 0x1000;
void *MemoryAllocator::allocate(size_t size) {
size_t pg_size = (size + size_t{pageSize - 1}) & ~size_t{pageSize - 1};
size_t offset = (pg_size - size) & ~size_t{pointerAlignment - 1};
void *ptr;
// Two extra pages for metadata in front and guard page at the end
// Reserve the whole region as PROT_NONE...
if (int e = mlibc::sys_vm_map(nullptr, pg_size + pageSize * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0, &ptr))
mlibc::panicLogger() << "sys_vm_map failed in MemoryAllocator::allocate (errno " << e << ")" << frg::endlog;
// ...Then replace pages to make them accessible, excluding the guard page
if (int e = mlibc::sys_vm_map(ptr, pg_size + pageSize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0, &ptr))
mlibc::panicLogger() << "sys_vm_map failed in MemoryAllocator::allocate (errno " << e << ")" << frg::endlog;
void *meta = ptr;
void *out_page = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(ptr) + pageSize);
void *out = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(out_page) + offset);
void *out_align_area = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(out) + size);
AllocatorMeta metaData{size, pg_size, allocatorMagic};
memset(meta, metaAreaValue, pageSize);
memcpy(meta, &metaData, sizeof(AllocatorMeta));
memset(out_page, offsetAreaValue, offset);
memset(out, allocatedAreaValue, size);
memset(out_align_area, alignmentAreaValue, pg_size - offset - size);
if constexpr (logAllocations)
mlibc::infoLogger() << "MemoryAllocator::allocate(" << size << ") = " << out << frg::endlog;
return out;
}
void MemoryAllocator::free(void *ptr) {
if (!ptr)
return;
if constexpr (logAllocations)
mlibc::infoLogger() << "MemoryAllocator::free(" << ptr << ")" << frg::endlog;
uintptr_t page_addr = reinterpret_cast<uintptr_t>(ptr) & ~size_t{pageSize - 1};
AllocatorMeta *meta = reinterpret_cast<AllocatorMeta *>(page_addr - pageSize);
if (meta->magic != allocatorMagic)
mlibc::panicLogger() << "Invalid allocator metadata magic in MemoryAllocator::free" << frg::endlog;
deallocate(ptr, meta->allocatedSize);
}
void MemoryAllocator::deallocate(void *ptr, size_t size) {
if (!ptr)
return;
if constexpr (logAllocations)
mlibc::infoLogger() << "MemoryAllocator::deallocate(" << ptr << ", " << size << ")" << frg::endlog;
uintptr_t page_addr = reinterpret_cast<uintptr_t>(ptr) & ~size_t{pageSize - 1};
AllocatorMeta *meta = reinterpret_cast<AllocatorMeta *>(page_addr - pageSize);
if (meta->magic != allocatorMagic)
mlibc::panicLogger() << "Invalid allocator metadata magic in MemoryAllocator::deallocate" << frg::endlog;
if (size != meta->allocatedSize)
mlibc::panicLogger() << "Invalid allocated size in metadata in MemoryAllocator::deallocate (given " << size << ", stored " << meta->allocatedSize << ")" << frg::endlog;
if constexpr (neverReleaseVa) {
void *unused;
if (int e = mlibc::sys_vm_map(meta, meta->pagesSize + pageSize * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0, &unused))
mlibc::panicLogger() << "sys_vm_map failed in MemoryAllocator::deallocate (errno " << e << ")" << frg::endlog;
} else {
if (int e = mlibc::sys_vm_unmap(meta, meta->pagesSize + pageSize * 2))
mlibc::panicLogger() << "sys_vm_unmap failed in MemoryAllocator::deallocate (errno " << e << ")" << frg::endlog;
}
}
void *MemoryAllocator::reallocate(void *ptr, size_t size) {
if (!size) {
free(ptr);
return nullptr;
}
void *newArea = allocate(size);
if (ptr) {
uintptr_t page_addr = reinterpret_cast<uintptr_t>(ptr) & ~size_t{pageSize - 1};
AllocatorMeta *meta = reinterpret_cast<AllocatorMeta *>(page_addr - pageSize);
if (meta->magic != allocatorMagic)
mlibc::panicLogger() << "Invalid allocator metadata magic in MemoryAllocator::reallocate" << frg::endlog;
memcpy(newArea, ptr, frg::min(meta->allocatedSize, size));
deallocate(ptr, meta->allocatedSize);
}
if constexpr (logAllocations)
mlibc::infoLogger() << "MemoryAllocator::reallocate(" << ptr << ", " << size << ") = " << newArea << frg::endlog;
return newArea;
}
size_t MemoryAllocator::get_size(void *ptr) {
if constexpr (logAllocations)
mlibc::infoLogger() << "MemoryAllocator::get_size(" << ptr << ")" << frg::endlog;
uintptr_t page_addr = reinterpret_cast<uintptr_t>(ptr) & ~size_t{pageSize - 1};
AllocatorMeta *meta = reinterpret_cast<AllocatorMeta *>(page_addr - pageSize);
if (meta->magic != allocatorMagic)
mlibc::panicLogger() << "Invalid allocator metadata magic in MemoryAllocator::get_size" << frg::endlog;
return meta->allocatedSize;
}
MemoryAllocator &getAllocator() {
// use frg::eternal to prevent a call to __cxa_atexit().
// this is necessary because __cxa_atexit() call this function.
static frg::eternal<MemoryAllocator> singleton{};
return singleton.get();
}
#endif /* !MLIBC_DEBUG_ALLOCATOR */
@@ -0,0 +1,268 @@
#include <bits/ensure.h>
#include <frg/string.hpp>
#include <mlibc/charcode.hpp>
#include <mlibc/debug.hpp>
namespace mlibc {
struct utf8_charcode {
static constexpr bool preserves_7bit_units = true;
static constexpr bool has_shift_states = false;
struct decode_state {
decode_state()
: _progress{0}, _cpoint{0} { }
auto progress() { return _progress; }
auto cpoint() { return _cpoint; }
charcode_error operator() (code_seq<const char> &seq) {
auto uc = static_cast<unsigned char>(*seq.it);
if(!_progress) {
if(!(uc & 0b1000'0000)) {
// ASCII-compatible.
_cpoint = uc;
}else if((uc & 0b1110'0000) == 0b1100'0000) {
_cpoint = uc & 0b1'1111;
_progress = 1;
}else if((uc & 0b1111'0000) == 0b1110'0000) {
_cpoint = uc & 0b1111;
_progress = 2;
}else if((uc & 0b1111'1000) == 0b1111'0000) {
_cpoint = uc & 0b111;
_progress = 3;
}else{
// If the highest two bits are 0b10, this is the second (or later) unit.
// Units with highest five bits = 0b11111 do not occur in valid UTF-8.
__ensure((uc & 0b1100'0000) == 0b1000'0000
|| (uc & 0b1111'1000) == 0b1111'1000);
return charcode_error::illegal_input;
}
}else{
// TODO: Return an error.
__ensure((uc & 0b1100'0000) == 0b1000'0000);
_cpoint = (_cpoint << 6) | (uc & 0x3F);
--_progress;
}
++seq.it;
return charcode_error::null;
}
private:
int _progress;
codepoint _cpoint;
};
#define NSEQ_STORE(VAL) do { \
if (!static_cast<bool>(nseq)) { \
return charcode_error::output_overflow; \
} \
*nseq.it = (VAL); \
++nseq.it; \
} while (0)
struct encode_state {
// Encodes a single character from wseq + the current state and stores it in nseq.
// TODO: Convert decode_state to the same strategy.
charcode_error operator() (code_seq<char> &nseq, code_seq<const codepoint> &wseq) {
auto wc = *wseq.it;
if (wc <= 0x7F) {
NSEQ_STORE(wc);
} else if (wc <= 0x7FF) {
NSEQ_STORE(0xC0 | (wc >> 6));
NSEQ_STORE(0x80 | (wc & 0x3f));
} else if (wc <= 0xFFFF) {
NSEQ_STORE(0xE0 | (wc >> 12));
NSEQ_STORE(0x80 | ((wc >> 6) & 0x3f));
NSEQ_STORE(0x80 | (wc & 0x3f));
} else if (wc <= 0x10FFFF) {
NSEQ_STORE(0xF0 | (wc >> 18));
NSEQ_STORE(0x80 | ((wc >> 12) & 0x3f));
NSEQ_STORE(0x80 | ((wc >> 6) & 0x3f));
NSEQ_STORE(0x80 | (wc & 0x3f));
} else {
return charcode_error::illegal_input;
}
++wseq.it;
return charcode_error::null;
}
};
#undef NSEQ_STORE
};
polymorphic_charcode::~polymorphic_charcode() = default;
// For *decoding, this class assumes that:
// - G::decode_state has members progress() and cpoint().
// - G::decode_state::progress() >= 0 at all times.
// TODO: This will be needed on platforms like Windows, where wchar_t is UTF-16.
// TODO: There, we can use negative __mlibc_mbstate::progress to represent encoding to UTF-16.
// - If G::decode_state::progress() == 0, the code point (given by cpoint())
// was decoded successfully.
template<typename G>
struct polymorphic_charcode_adapter : polymorphic_charcode {
polymorphic_charcode_adapter()
: polymorphic_charcode{G::preserves_7bit_units, G::has_shift_states} { }
charcode_error decode(code_seq<const char> &nseq, code_seq<codepoint> &wseq,
__mlibc_mbstate &st) override {
__ensure(!st.__progress); // TODO: Update st with ds.progress() and ds.cpoint().
code_seq<const char> decode_nseq = nseq;
typename G::decode_state ds;
while(decode_nseq && wseq) {
// Consume the next code unit.
if(auto e = ds(decode_nseq); e != charcode_error::null)
return e;
// Produce a new code point.
if(!ds.progress()) {
// "Commit" consumed code units (as there was no decode error).
nseq.it = decode_nseq.it;
if(!ds.cpoint()) // Stop on null characters.
return charcode_error::null;
*wseq.it = ds.cpoint();
++wseq.it;
}
}
if(ds.progress())
return charcode_error::input_underflow;
return charcode_error::null;
}
charcode_error decode_wtranscode(code_seq<const char> &nseq, code_seq<wchar_t> &wseq,
__mlibc_mbstate &st) override {
__ensure(!st.__progress); // TODO: Update st with ds.progress() and ds.cpoint().
code_seq<const char> decode_nseq = nseq;
typename G::decode_state ds;
while(decode_nseq && wseq) {
// Consume the next code unit.
if(auto e = ds(decode_nseq); e != charcode_error::null)
return e;
// Produce a new code point.
if(!ds.progress()) {
nseq.it = decode_nseq.it;
// "Commit" consumed code units (as there was no decode error).
if(!ds.cpoint()) // Stop on null characters.
return charcode_error::null;
*wseq.it = ds.cpoint();
++wseq.it;
}
}
if(ds.progress())
return charcode_error::input_underflow;
return charcode_error::null;
}
charcode_error decode_wtranscode_length(code_seq<const char> &nseq, size_t *n,
__mlibc_mbstate &st) override {
__ensure(!st.__progress); // TODO: Update st with ds.progress() and ds.cpoint().
code_seq<const char> decode_nseq = nseq;
typename G::decode_state ds;
*n = 0;
while(decode_nseq) {
// Consume the next code unit.
if(auto e = ds(decode_nseq); e != charcode_error::null)
return e;
if(!ds.progress()) {
nseq.it = decode_nseq.it;
// "Commit" consumed code units (as there was no decode error).
if(!ds.cpoint()) // Stop on null code points.
return charcode_error::null;
++(*n);
}
}
if(ds.progress())
return charcode_error::input_underflow;
return charcode_error::null;
}
charcode_error encode_wtranscode(code_seq<char> &nseq, code_seq<const wchar_t> &wseq,
__mlibc_mbstate &st) override {
__ensure(!st.__progress); // TODO: Update st with es.progress() and es.cpoint().
code_seq<char> encode_nseq = nseq;
typename G::encode_state es;
while(encode_nseq && wseq) {
codepoint cp = *wseq.it;
if(!cp)
return charcode_error::null;
code_seq<const codepoint> cps{&cp, &cp + 1};
if(auto e = es(encode_nseq, cps); e == charcode_error::dirty) {
continue;
}else if(e != charcode_error::null) {
return e;
}
__ensure(cps.it == cps.end);
++wseq.it;
// "Commit" produced code units (as there was no encode error).
nseq.it = encode_nseq.it;
}
if(encode_nseq.it != nseq.it)
return charcode_error::output_overflow;
return charcode_error::null;
}
charcode_error encode_wtranscode_length(code_seq<const wchar_t> &wseq, size_t *n,
__mlibc_mbstate &st) override {
__ensure(!st.__progress); // TODO: Update st with es.progress() and es.cpoint().
typename G::encode_state es;
*n = 0;
while(wseq) {
char temp[4];
code_seq<char> encode_nseq{temp, temp + 4};
codepoint cp = *wseq.it;
if(!cp)
return charcode_error::null;
// Consume the next code unit.
code_seq<const codepoint> cps{&cp, &cp + 1};
if(auto e = es(encode_nseq, cps); e == charcode_error::dirty) {
continue;
}else if(e != charcode_error::null) {
return e;
}
++(*n);
++wseq.it;
}
return charcode_error::null;
}
};
polymorphic_charcode *current_charcode() {
static polymorphic_charcode_adapter<utf8_charcode> global_charcode;
return &global_charcode;
}
charcode_error wide_charcode::promote(wchar_t nc, codepoint &wc) {
// TODO: Allow non-identity encodings of wchar_t.
wc = nc;
return charcode_error::null;
}
wide_charcode *platform_wide_charcode() {
static wide_charcode global_wide_charcode;
return &global_wide_charcode;
}
} // namespace mlibc
@@ -0,0 +1,144 @@
#include <bits/ensure.h>
#include <mlibc/charset.hpp>
#include <mlibc/debug.hpp>
namespace mlibc {
bool charset::is_ascii_superset() {
// TODO: For locales that change the meaning of ASCII chars, this needs to be changed.
return true;
}
bool charset::is_alpha(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_alpha() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_digit(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return c >= '0' && c <= '9';
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_digit() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_xdigit(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_xdigit() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_alnum(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_alnum() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_punct(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return c == '!' || c == '"' || c == '#' || c == '$' || c == '%' || c == '&'
|| c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' || c == ','
|| c == '-' || c == '.' || c == '/'
|| c == ':' || c == ';' || c == '<' || c == '=' || c == '>' || c == '?'
|| c == '@'
|| c == '[' || c == '\\' || c == ']' || c == '^' || c == '_' || c == '`'
|| c == '{' || c == '|' || c == '}' || c == '~';
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_punct() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_graph(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return c >= 0x21 && c <= 0x7E;
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_graph() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_blank(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return c == ' ' || c == '\t';
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_blank() is not implemented"
" for the full Unicode charset " << c << frg::endlog;
return false;
}
bool charset::is_space(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_space() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_print(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return c >= 0x20 && c <= 0x7E;
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_print() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_lower(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return (c >= 'a' && c <= 'z');
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_print() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
bool charset::is_upper(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
return (c >= 'A' && c <= 'Z');
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::is_print() is not implemented"
" for the full Unicode charset" << frg::endlog;
return false;
}
codepoint charset::to_lower(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
if(c >= 'A' && c <= 'Z')
return c - 'A' + 'a';
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::to_lower() is not implemented"
" for the full Unicode charset" << frg::endlog;
return c;
}
codepoint charset::to_upper(codepoint c) {
if(c <= 0x7F && is_ascii_superset())
if(c >= 'a' && c <= 'z')
return c - 'a' + 'A';
if(c > 0x7F)
mlibc::infoLogger() << "mlibc: charset::to_upper() is not implemented"
" for the full Unicode charset" << frg::endlog;
return c;
}
charset *current_charset() {
static charset global_charset;
return &global_charset;
}
} // namespace mlibc
@@ -0,0 +1,22 @@
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
#include <mlibc/internal-sysdeps.hpp>
namespace mlibc {
frg::stack_buffer_logger<InfoSink, 512> infoLogger;
frg::stack_buffer_logger<PanicSink, 512> panicLogger;
void InfoSink::operator() (const char *message) {
sys_libc_log(message);
}
void PanicSink::operator() (const char *message) {
// sys_libc_log("mlibc: Write to PanicSink");
sys_libc_log(message);
sys_libc_panic();
}
} // namespace mlibc
@@ -0,0 +1,18 @@
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
void __ensure_fail(const char *assertion, const char *file, unsigned int line,
const char *function) {
mlibc::panicLogger() << "In function " << function
<< ", file " << file << ":" << line << "\n"
<< "__ensure(" << assertion << ") failed" << frg::endlog;
}
void __ensure_warn(const char *assertion, const char *file, unsigned int line,
const char *function) {
mlibc::infoLogger() << "In function " << function
<< ", file " << file << ":" << line << "\n"
<< "__ensure(" << assertion << ") failed" << frg::endlog;
}
@@ -0,0 +1,215 @@
#include <string.h>
#include <stdint.h>
namespace {
template<typename T>
[[gnu::always_inline]]
inline T alias_load(const unsigned char *&p) {
T value;
__builtin_memcpy(&value, p, sizeof(value));
p += sizeof(T);
return value;
}
template<typename T>
[[gnu::always_inline]]
inline void alias_store(unsigned char *&p, T value) {
__builtin_memcpy(p, &value, sizeof(value));
p += sizeof(T);
}
#if defined(__LP64__) && !defined(__riscv)
void *forward_copy(void *__restrict dest, const void *__restrict src, size_t n) {
auto curDest = reinterpret_cast<unsigned char *>(dest);
auto curSrc = reinterpret_cast<const unsigned char *>(src);
while(n >= 8 * 8) {
auto w1 = alias_load<uint64_t>(curSrc);
auto w2 = alias_load<uint64_t>(curSrc);
auto w3 = alias_load<uint64_t>(curSrc);
auto w4 = alias_load<uint64_t>(curSrc);
auto w5 = alias_load<uint64_t>(curSrc);
auto w6 = alias_load<uint64_t>(curSrc);
auto w7 = alias_load<uint64_t>(curSrc);
auto w8 = alias_load<uint64_t>(curSrc);
alias_store<uint64_t>(curDest, w1);
alias_store<uint64_t>(curDest, w2);
alias_store<uint64_t>(curDest, w3);
alias_store<uint64_t>(curDest, w4);
alias_store<uint64_t>(curDest, w5);
alias_store<uint64_t>(curDest, w6);
alias_store<uint64_t>(curDest, w7);
alias_store<uint64_t>(curDest, w8);
n -= 8 * 8;
}
if(n >= 4 * 8) {
auto w1 = alias_load<uint64_t>(curSrc);
auto w2 = alias_load<uint64_t>(curSrc);
auto w3 = alias_load<uint64_t>(curSrc);
auto w4 = alias_load<uint64_t>(curSrc);
alias_store<uint64_t>(curDest, w1);
alias_store<uint64_t>(curDest, w2);
alias_store<uint64_t>(curDest, w3);
alias_store<uint64_t>(curDest, w4);
n -= 4 * 8;
}
if(n >= 2 * 8) {
auto w1 = alias_load<uint64_t>(curSrc);
auto w2 = alias_load<uint64_t>(curSrc);
alias_store<uint64_t>(curDest, w1);
alias_store<uint64_t>(curDest, w2);
n -= 2 * 8;
}
if(n >= 8) {
auto w = alias_load<uint64_t>(curSrc);
alias_store<uint64_t>(curDest, w);
n -= 8;
}
if(n >= 4) {
auto w = alias_load<uint32_t>(curSrc);
alias_store<uint32_t>(curDest, w);
n -= 4;
}
if(n >= 2) {
auto w = alias_load<uint16_t>(curSrc);
alias_store<uint16_t>(curDest, w);
n -= 2;
}
if(n)
*curDest = *curSrc;
return dest;
}
#else // !__LP64__
void *forward_copy(void *dest, const void *src, size_t n) {
for(size_t i = 0; i < n; i++)
((char *)dest)[i] = ((const char *)src)[i];
return dest;
}
#endif // __LP64__ / !__LP64__
} // namespace
// --------------------------------------------------------------------------------------
// memcpy() implementation.
// --------------------------------------------------------------------------------------
void *memcpy(void *__restrict dest, const void *__restrict src, size_t n) {
return forward_copy(dest, src, n);
}
// --------------------------------------------------------------------------------------
// memset() implementation.
// --------------------------------------------------------------------------------------
#ifdef __LP64__
void *memset(void *dest, int val, size_t n) {
auto curDest = reinterpret_cast<unsigned char *>(dest);
unsigned char byte = val;
// Get rid of misalignment.
while(n && (uintptr_t(curDest) & 7)) {
*curDest++ = byte;
--n;
}
auto pattern64 = static_cast<uint64_t>(
static_cast<uint64_t>(byte)
| (static_cast<uint64_t>(byte) << 8)
| (static_cast<uint64_t>(byte) << 16)
| (static_cast<uint64_t>(byte) << 24)
| (static_cast<uint64_t>(byte) << 32)
| (static_cast<uint64_t>(byte) << 40)
| (static_cast<uint64_t>(byte) << 48)
| (static_cast<uint64_t>(byte) << 56));
auto pattern32 = static_cast<uint32_t>(
static_cast<uint32_t>(byte)
| (static_cast<uint32_t>(byte) << 8)
| (static_cast<uint32_t>(byte) << 16)
| (static_cast<uint32_t>(byte) << 24));
auto pattern16 = static_cast<uint16_t>(
static_cast<uint16_t>(byte)
| (static_cast<uint16_t>(byte) << 8));
while(n >= 8 * 8) {
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
n -= 8 * 8;
}
if(n >= 4 * 8) {
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
n -= 4 * 8;
}
if(n >= 2 * 8) {
alias_store<uint64_t>(curDest, pattern64);
alias_store<uint64_t>(curDest, pattern64);
n -= 2 * 8;
}
if(n >= 8) {
alias_store<uint64_t>(curDest, pattern64);
n -= 8;
}
if(n >= 4) {
alias_store<uint32_t>(curDest, pattern32);
n -= 4;
}
if(n >= 2) {
alias_store<uint16_t>(curDest, pattern16);
n -= 2;
}
if(n)
*curDest = byte;
return dest;
}
#else // !__LP64__
void *memset(void *dest, int byte, size_t count) {
for(size_t i = 0; i < count; i++)
((char *)dest)[i] = (char)byte;
return dest;
}
#endif // __LP64__ / !__LP64__
// --------------------------------------------------------------------------------------
// "Non-optimized" functions.
// --------------------------------------------------------------------------------------
void *memmove(void *dest, const void *src, size_t size) {
// Use uintptr_t for pointer comparisons because otherwise it's undefined behaviour
// when dest and src point to different objects.
uintptr_t udest = reinterpret_cast<uintptr_t>(dest);
uintptr_t usrc = reinterpret_cast<uintptr_t>(src);
if(udest < usrc || usrc + size <= udest) {
return forward_copy(dest, src, size);
} else if(udest > usrc) {
char *dest_bytes = (char *)dest;
char *src_bytes = (char *)src;
for(size_t i = 0; i < size; i++)
dest_bytes[size - i - 1] = src_bytes[size - i - 1];
}
return dest;
}
size_t strlen(const char *s) {
size_t len = 0;
for(size_t i = 0; s[i]; i++)
len++;
return len;
}
@@ -0,0 +1,14 @@
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
#include <mlibc/internal-sysdeps.hpp>
extern "C" void frg_panic(const char *mstr) {
// mlibc::sys_libc_log("mlibc: Call to frg_panic");
mlibc::sys_libc_log(mstr);
mlibc::sys_libc_panic();
}
extern "C" void frg_log(const char *mstr) {
mlibc::sys_libc_log(mstr);
}
@@ -0,0 +1,393 @@
#include <assert.h>
#include <bits/ensure.h>
#include <bits/getopt.h>
#include <frg/optional.hpp>
#include <mlibc-config.h>
#include <mlibc/debug.hpp>
#include <mlibc/getopt.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <variant>
char *optarg;
int optind = 1;
int opterr = 1;
int optopt;
namespace {
int __optpos = 1;
int getopt_common_internal(int argc, char * const argv[], const char *optstring, const struct option *longopts,
int *longindex, enum mlibc::GetoptMode mode) {
// find a matching longopt for an `arg` of length `n`
// returns a size_t of the index of the matched longopt, preferring an exact over a partial match
// returns a char of the error that getopt should return if multiple matches were available
// returns a std::monostate if no longopt resulted in a exact or partial match
auto longopt_find = [&](const char *arg, size_t n) -> std::variant<size_t, char, std::monostate> {
assert(mode != mlibc::GetoptMode::Short);
frg::optional<size_t> i = frg::null_opt;
// first, attempt to find exactly one exact match
for(size_t longopt = 0; longopts[longopt].name; longopt++) {
if(strncmp(arg, longopts[longopt].name, n) || longopts[longopt].name[n])
continue;
if(i) {
if(opterr)
fprintf(stderr, "Multiple option declaration detected: %s\n", arg);
optind++;
return '?';
}
i = longopt;
}
if(i)
return *i;
// because no exact match was found, we now search for longopts with partial matches
for(size_t longopt = 0; longopts[longopt].name; longopt++) {
if(strncmp(arg, longopts[longopt].name, n))
continue;
if(i) {
if(opterr)
fprintf(stderr, "Multiple option declaration detected: %s\n", arg);
optind++;
return '?';
}
i = longopt;
}
if(i)
return *i;
return std::monostate{};
};
auto longopt_consume = [&](const char *arg, char *s, int k, bool colon) -> frg::optional<int> {
assert(mode != mlibc::GetoptMode::Short);
// Consume the option and its argument.
if(longopts[k].has_arg == required_argument) {
if(s) {
// Consume the long option and its argument.
optarg = s + 1;
optind++;
}else if(optind + 1 < argc && argv[optind + 1]) {
// Consume the long option.
optind++;
// Consume the option's argument.
optarg = argv[optind];
optind++;
}else{
/* If an error was detected, and the first character of optstring is not a colon,
and the external variable opterr is nonzero (which is the default),
getopt() prints an error message. */
if(!colon && opterr)
fprintf(stderr, "--%s requires an argument.\n", arg);
optopt = longopts[k].val;
optind++;
/* If the first character of optstring is a colon (':'), then getopt()
returns ':' instead of '?' to indicate a missing option argument. */
return colon ? ':' : '?';
}
}else if(longopts[k].has_arg == optional_argument) {
if(s) {
// Consume the long option and its argument.
optarg = s + 1;
optind++;
}else{
// Consume the long option.
optarg = nullptr;
optind++;
}
}else{
__ensure(longopts[k].has_arg == no_argument);
// did we get passed a value?
if(s && strlen(s)) {
optind++;
return colon ? ':' : '?';
}
// Consume the long option.
optind++;
optarg = nullptr;
}
return frg::null_opt;
};
bool colon = optstring[0] == ':';
bool stop_at_first_nonarg = (optstring[0] == '+' || getenv("POSIXLY_CORRECT"));
// if optstring contains "W;", then "-W foo" is treated as the long option "--foo".
bool w_long_options = [&]{
if(mode == mlibc::GetoptMode::Short)
return false;
const char *W = strchr(optstring, 'W');
if (!W)
return false;
return W[1] == ';';
}();
auto isOptionArg = [](char *arg){
// If the first character of arg '-', and the arg is not exactly
// equal to "-" or "--", then the arg is an option argument.
return arg[0] == '-' && strcmp(arg, "-") && strcmp(arg, "--");
};
while(optind < argc) {
char *arg = argv[optind];
if(!isOptionArg(arg)) {
if(!strcmp(arg, "--")) {
optind++;
return -1;
}
if(stop_at_first_nonarg) {
optarg = nullptr;
return -1;
}
bool further_options = false;
int skip = optind;
for(; skip < argc; ++skip) {
if(isOptionArg(argv[skip])) {
further_options = true;
break;
}
}
if(further_options) {
optind += skip - optind;
continue;
} else {
optarg = nullptr;
return -1;
}
}
if(arg[1] == '-' && mode != mlibc::GetoptMode::Short) {
arg += 2;
// Determine the end of the option name (vs. the start of the argument).
auto s = strchr(arg, '=');
size_t n = s ? (s - arg) : strlen(arg);
auto k = longopt_find(arg, n);
if(std::holds_alternative<std::monostate>(k)) {
if(opterr)
fprintf(stderr, "--%s is not a valid option.\n", arg);
optind++;
return '?';
} else if(std::holds_alternative<char>(k)) {
return std::get<char>(k);
}
if(longindex)
*longindex = std::get<size_t>(k);
if(auto r = longopt_consume(arg, s, std::get<size_t>(k), colon); r)
return r.value();
if(!longopts[std::get<size_t>(k)].flag) {
return longopts[std::get<size_t>(k)].val;
}else{
*longopts[std::get<size_t>(k)].flag = longopts[std::get<size_t>(k)].val;
return 0;
}
}else{
/* handle short options, i.e. options with only one dash prefixed; e.g. `program -s` */
unsigned int i = __optpos;
while(true) {
if(mode == mlibc::GetoptMode::LongOnly) {
const char *lo_arg = &arg[1];
auto s = strchr(lo_arg, '=');
size_t n = s ? (s - lo_arg) : strlen(lo_arg);
auto longopt_res = longopt_find(lo_arg, n);
if(std::holds_alternative<char>(longopt_res)) {
return std::get<char>(longopt_res);
} else if(std::holds_alternative<size_t>(longopt_res)) {
auto k = std::get<size_t>(longopt_res);
if(auto r = longopt_consume(lo_arg, s, k, colon); r)
return r.value();
if(!longopts[k].flag) {
return longopts[k].val;
}else{
*longopts[k].flag = longopts[k].val;
return 0;
}
}
}
auto opt = strchr(optstring, arg[i]);
if(opt) {
if(opt[0] == 'W' && w_long_options) {
const char *lo_arg = [&]() {
if(opt[1]) {
return &arg[i] + 1;
} else {
return &arg[i + 1];
}
}();
auto s = strchr(lo_arg, '=');
size_t n = s ? (s - lo_arg) : strlen(lo_arg);
if(!n) {
optopt = 'W';
optind++;
return colon ? ':' : '?';
}
auto longopt_res = longopt_find(lo_arg, n);
if(std::holds_alternative<char>(longopt_res)) {
return std::get<char>(longopt_res);
} else if(std::holds_alternative<size_t>(longopt_res)) {
auto k = std::get<size_t>(longopt_res);
if(auto r = longopt_consume(lo_arg, s, k, colon); r)
return r.value();
if(!longopts[k].flag) {
return longopts[k].val;
}else{
*longopts[k].flag = longopts[k].val;
return 0;
}
} else {
optind++;
return colon ? ':' : '?';
}
} else if(opt[1] == ':') {
// one colon means the option requires an argument
// two colons mean the option takes an optional argument as part of the
// same argv element (in the same word as the option name itself)
bool required = (opt[2] != ':');
if(arg[i+1]) {
optarg = arg + i + 1;
} else if(optind + 1 < argc && argv[optind + 1] && required && argv[optind + 1][0] != '-') {
/* there is an argument to this short option, separated by a space,
* and the shortopt specification does not specify an optional arg */
optarg = argv[optind + 1];
optind++;
__optpos = 1;
} else if(!required) {
optarg = nullptr;
} else {
__optpos = 1;
optopt = arg[i];
return colon ? ':' : '?';
}
optind++;
} else {
if(arg[i+1]) {
__optpos++;
} else if(arg[i]) {
optind++;
} else {
optarg = nullptr;
return -1;
}
}
return arg[i];
} else {
/* If getopt() does not recognize an option character, it prints an error message to stderr,
stores the character in optopt, and returns '?'. The calling program may prevent
the error message by setting opterr to 0. */
optopt = arg[1];
if(opterr)
fprintf(stderr, "%s is not a valid option.\n", arg);
optind++;
return '?';
}
}
}
}
optarg = nullptr;
return -1;
}
void permute(char **argv, int dest, int src) {
assert(src > dest);
char *tmp = argv[src];
for(int i = src; i > dest; i--) {
argv[i] = argv[i - 1];
}
argv[dest] = tmp;
}
} // namespace
#if __MLIBC_BSD_OPTION
extern "C" int optreset;
#endif /*__MLIBC_BSD_OPTION */
namespace mlibc {
int getopt_common(int argc, char * const argv[], const char *optstring,
const struct option *longopts, int *longindex, enum GetoptMode mode) {
// glibc extension: Setting optind to zero causes a full reset.
// TODO: Should we really reset opterr and the other flags?
if(!optind
#if __MLIBC_BSD_OPTION
|| optreset
#endif //__MLIBC_BSD_OPTION
) {
optarg = nullptr;
optind = 1;
optopt = 0;
__optpos = 1;
#if __MLIBC_BSD_OPTION
optreset = 0;
#endif //__MLIBC_BSD_OPTION
}
int skipped = optind;
if(optstring[0] != '+' && optstring[0] != '-') {
int i = optind;
for(;; i++) {
if(i >= argc || !argv[i]) {
optarg = nullptr;
return -1;
}
if(argv[i][0] == '-' && argv[i][1])
break;
}
optind = i;
}
int resumed = optind;
auto ret = getopt_common_internal(argc, argv, optstring, longopts, longindex, mode);
if(resumed > skipped) {
for(int i = 0; i < (optind - resumed); i++) {
permute(const_cast<char **>(argv), skipped, optind - 1);
}
optind = skipped + (optind - resumed);
}
return ret;
}
} // namespace mlibc
@@ -0,0 +1,27 @@
#include <stdlib.h>
#include <string.h>
#include <mlibc/global-config.hpp>
namespace mlibc {
struct GlobalConfigGuard {
GlobalConfigGuard();
};
GlobalConfigGuard guard;
GlobalConfigGuard::GlobalConfigGuard() {
// Force the config to be created during initialization of libc.so.
mlibc::globalConfig();
}
static bool envEnabled(const char *env) {
auto value = getenv(env);
return value && *value && *value != '0';
}
GlobalConfig::GlobalConfig() {
debugMalloc = envEnabled("MLIBC_DEBUG_MALLOC");
}
} // namespace mlibc
@@ -0,0 +1,16 @@
// This translation unit provides symbols for functions marked with __MLIBC_INLINE_DEFINITION.
// All headers with such functions must be included here.
#define __MLIBC_EMIT_INLINE_DEFINITIONS
#include <mlibc-config.h>
#include <elf.h>
#if __MLIBC_LINUX_OPTION
#include <sys/sysmacros.h>
#endif /* __MLIBC_LINUX_OPTION */
#ifndef MLIBC_BUILDING_RTLD
#include <math.h>
#endif
@@ -0,0 +1,99 @@
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
#include <mlibc/locale.hpp>
namespace mlibc {
char *nl_langinfo(nl_item item) {
if(item == CODESET) {
return const_cast<char *>("UTF-8");
} else if(item >= ABMON_1 && item <= ABMON_12) {
switch(item) {
case ABMON_1: return const_cast<char *>("Jan");
case ABMON_2: return const_cast<char *>("Feb");
case ABMON_3: return const_cast<char *>("Mar");
case ABMON_4: return const_cast<char *>("Apr");
case ABMON_5: return const_cast<char *>("May");
case ABMON_6: return const_cast<char *>("Jun");
case ABMON_7: return const_cast<char *>("Jul");
case ABMON_8: return const_cast<char *>("Aug");
case ABMON_9: return const_cast<char *>("Sep");
case ABMON_10: return const_cast<char *>("Oct");
case ABMON_11: return const_cast<char *>("Nov");
case ABMON_12: return const_cast<char *>("Dec");
default:
__ensure(!"ABMON_* constants don't seem to be contiguous!");
__builtin_unreachable();
}
} else if(item >= MON_1 && item <= MON_12) {
switch(item) {
case MON_1: return const_cast<char *>("January");
case MON_2: return const_cast<char *>("Feburary");
case MON_3: return const_cast<char *>("March");
case MON_4: return const_cast<char *>("April");
case MON_5: return const_cast<char *>("May");
case MON_6: return const_cast<char *>("June");
case MON_7: return const_cast<char *>("July");
case MON_8: return const_cast<char *>("August");
case MON_9: return const_cast<char *>("September");
case MON_10: return const_cast<char *>("October");
case MON_11: return const_cast<char *>("November");
case MON_12: return const_cast<char *>("December");
default:
__ensure(!"MON_* constants don't seem to be contiguous!");
__builtin_unreachable();
}
} else if(item == AM_STR) {
return const_cast<char *>("AM");
} else if(item == PM_STR) {
return const_cast<char *>("PM");
} else if(item >= DAY_1 && item <= DAY_7) {
switch(item) {
case DAY_1: return const_cast<char *>("Sunday");
case DAY_2: return const_cast<char *>("Monday");
case DAY_3: return const_cast<char *>("Tuesday");
case DAY_4: return const_cast<char *>("Wednesday");
case DAY_5: return const_cast<char *>("Thursday");
case DAY_6: return const_cast<char *>("Friday");
case DAY_7: return const_cast<char *>("Saturday");
default:
__ensure(!"DAY_* constants don't seem to be contiguous!");
__builtin_unreachable();
}
} else if(item >= ABDAY_1 && item <= ABDAY_7) {
switch(item) {
case ABDAY_1: return const_cast<char *>("Sun");
case ABDAY_2: return const_cast<char *>("Mon");
case ABDAY_3: return const_cast<char *>("Tue");
case ABDAY_4: return const_cast<char *>("Wed");
case ABDAY_5: return const_cast<char *>("Thu");
case ABDAY_6: return const_cast<char *>("Fri");
case ABDAY_7: return const_cast<char *>("Sat");
default:
__ensure(!"ABDAY_* constants don't seem to be contiguous!");
__builtin_unreachable();
}
}else if(item == D_FMT) {
return const_cast<char *>("%m/%d/%y");
}else if(item == T_FMT) {
return const_cast<char *>("%H:%M:%S");
}else if(item == T_FMT_AMPM) {
return const_cast<char *>("%I:%M:%S %p");
}else if(item == D_T_FMT) {
return const_cast<char *>("%a %b %e %T %Y");
} else if (item == RADIXCHAR) {
return const_cast<char *>(".");
} else if (item == THOUSEP) {
return const_cast<char *>("");
}else if(item == YESEXPR) {
return const_cast<char *>("^[yY]");
}else if(item == NOEXPR) {
return const_cast<char *>("^[nN]");
}else{
mlibc::infoLogger() << "mlibc: nl_langinfo item "
<< item << " is not implemented properly" << frg::endlog;
return const_cast<char *>("");
}
}
} // namespace mlibc
@@ -0,0 +1,98 @@
#include <mlibc/search.hpp>
#include <frg/string.hpp>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
struct _ENTRY {
ENTRY entry;
bool used;
};
namespace mlibc {
int hcreate_r(size_t num_entries, struct hsearch_data *htab) {
if(!htab) {
errno = EINVAL;
return 0;
}
htab->table = static_cast<_ENTRY*>(calloc(num_entries, sizeof(_ENTRY)));
if(!htab->table) {
errno = ENOMEM;
return 0;
}
htab->filled = 0;
htab->size = num_entries;
return 1;
}
void hdestroy_r(struct hsearch_data *htab) {
if(!htab) {
errno = EINVAL;
return;
}
free(htab->table);
htab->table = nullptr;
htab->size = 0;
htab->filled = 0;
}
int hsearch_r(ENTRY item, ACTION action, ENTRY **ret, struct hsearch_data *htab) {
auto key = frg::string_view{item.key};
auto hash = frg::hash<frg::string_view>{}(key);
size_t bucket_index = hash % htab->size;
size_t start = bucket_index;
while(true) {
auto &bucket = htab->table[bucket_index];
if(bucket.used) {
if(bucket.entry.key == key) {
*ret = &bucket.entry;
return 1;
}
} else if(action == FIND) {
errno = ESRCH;
*ret = nullptr;
return 0;
}
bucket_index = (bucket_index + 1) % htab->size;
if(bucket_index == start) {
if(action == FIND) {
errno = ESRCH;
*ret = nullptr;
return 0;
} else {
break;
}
}
}
// insert a new entry.
if(htab->size == htab->filled) {
errno = ENOMEM;
return 0;
}
++htab->filled;
bucket_index = start;
while(true) {
auto &bucket = htab->table[bucket_index];
if(!bucket.used) {
bucket.used = true;
bucket.entry = item;
*ret = &bucket.entry;
break;
}
bucket_index = (bucket_index + 1) % htab->size;
}
return 1;
}
} // namespace mlibc
@@ -0,0 +1,92 @@
#include <bits/sigset_t.h>
#include <bits/ensure.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stddef.h>
namespace {
template<class T> struct remove_reference { typedef T type; };
template<class T> struct remove_reference<T&> { typedef T type; };
// Assume that the struct has a member named 'sig'.
template<typename T>
struct sigset_type_helper {
using type = typename remove_reference<decltype(T::sig[0])>::type;
static_assert(offsetof(T, sig) == 0);
};
template<>
struct sigset_type_helper<unsigned long> { using type = unsigned long; };
template<>
struct sigset_type_helper<unsigned long long> { using type = unsigned long long; };
template<>
struct sigset_type_helper<long> { using type = long; };
template<>
struct sigset_type_helper<long long> { using type = long long; };
// Some ABIs define sigset_t as a simple integer (e.g unsigned long),
// while others define it as a struct containing an array of integers.
using sigset_underlying_type = sigset_type_helper<sigset_t>::type;
size_t signo_to_field(int signo) {
return signo / (sizeof(sigset_underlying_type) * CHAR_BIT);
}
size_t signo_to_bit(int signo) {
return signo % (sizeof(sigset_underlying_type) * CHAR_BIT);
}
} // namespace
int sigemptyset(sigset_t *sigset) {
memset(sigset, 0, sizeof(*sigset));
return 0;
}
int sigfillset(sigset_t *sigset) {
memset(sigset, ~0, sizeof(*sigset));
return 0;
}
int sigaddset(sigset_t *sigset, int sig) {
int signo = sig - 1;
if(signo < 0 || static_cast<unsigned int>(signo) >= (sizeof(sigset_t) * CHAR_BIT)) {
errno = EINVAL;
return -1;
}
auto ptr = reinterpret_cast<sigset_underlying_type *>(sigset);
auto field = signo_to_field(signo);
auto bit = signo_to_bit(signo);
ptr[field] |= (1UL << bit);
return 0;
}
int sigdelset(sigset_t *sigset, int sig) {
int signo = sig - 1;
if(signo < 0 || static_cast<unsigned int>(signo) >= (sizeof(sigset_t) * CHAR_BIT)) {
errno = EINVAL;
return -1;
}
auto ptr = reinterpret_cast<sigset_underlying_type *>(sigset);
auto field = signo_to_field(signo);
auto bit = signo_to_bit(signo);
ptr[field] &= ~(1UL << bit);
return 0;
}
int sigismember(const sigset_t *sigset, int sig) {
int signo = sig - 1;
if(signo < 0 || static_cast<unsigned int>(signo) >= (sizeof(sigset_t) * CHAR_BIT)) {
errno = EINVAL;
return -1;
}
auto ptr = reinterpret_cast<const sigset_underlying_type *>(sigset);
auto field = signo_to_field(signo);
auto bit = signo_to_bit(signo);
return (ptr[field] & (1UL << bit)) != 0;
}
@@ -0,0 +1,22 @@
#include <ctype.h>
#include <mlibc/strings.hpp>
namespace mlibc {
int strncasecmp(const char *a, const char *b, size_t size) {
for(size_t i = 0; i < size; i++) {
unsigned char a_byte = tolower(a[i]);
unsigned char b_byte = tolower(b[i]);
if(!a_byte && !b_byte)
return 0;
// If only one char is null, one of the following cases applies.
if(a_byte < b_byte)
return -1;
if(a_byte > b_byte)
return 1;
}
return 0;
}
} // namespace mlibc
@@ -0,0 +1,346 @@
#include <abi-bits/errno.h>
#include <bits/threads.h>
#include <bits/ensure.h>
#include <mlibc/all-sysdeps.hpp>
#include <mlibc/debug.hpp>
#include <mlibc/lock.hpp>
#include <mlibc/threads.hpp>
#include <mlibc/tcb.hpp>
extern "C" Tcb *__rtld_allocateTcb();
namespace mlibc {
int thread_create(struct __mlibc_thread_data **__restrict thread, const struct __mlibc_threadattr *__restrict attrp, void *entry, void *__restrict user_arg, bool returns_int) {
auto new_tcb = __rtld_allocateTcb();
pid_t tid;
struct __mlibc_threadattr attr = {};
if (!attrp)
thread_attr_init(&attr);
else
attr = *attrp;
if (attr.__mlibc_cpuset)
mlibc::infoLogger() << "pthread_create(): cpuset is ignored!" << frg::endlog;
if (attr.__mlibc_sigmaskset)
mlibc::infoLogger() << "pthread_create(): sigmask is ignored!" << frg::endlog;
// TODO: due to alignment guarantees, the stackaddr and stacksize might change
// when the stack is allocated. Currently this isn't propagated to the TCB,
// but it should be.
void *stack = attr.__mlibc_stackaddr;
if (!mlibc::sys_prepare_stack) {
MLIBC_MISSING_SYSDEP();
return ENOSYS;
}
int ret = mlibc::sys_prepare_stack(&stack, entry,
user_arg, new_tcb, &attr.__mlibc_stacksize, &attr.__mlibc_guardsize, &new_tcb->stackAddr);
if (ret)
return ret;
if (!mlibc::sys_clone) {
MLIBC_MISSING_SYSDEP();
return ENOSYS;
}
new_tcb->stackSize = attr.__mlibc_stacksize;
new_tcb->guardSize = attr.__mlibc_guardsize;
new_tcb->returnValueType = (returns_int) ? TcbThreadReturnValue::Integer : TcbThreadReturnValue::Pointer;
new_tcb->isJoinable = (attr.__mlibc_detachstate == __MLIBC_THREAD_CREATE_JOINABLE);
mlibc::sys_clone(new_tcb, &tid, stack);
*thread = reinterpret_cast<struct __mlibc_thread_data *>(new_tcb);
__atomic_store_n(&new_tcb->tid, tid, __ATOMIC_RELAXED);
mlibc::sys_futex_wake(&new_tcb->tid);
return 0;
}
int thread_join(struct __mlibc_thread_data *thread, void *ret) {
auto tcb = reinterpret_cast<Tcb *>(thread);
if(!tcb->isJoinable) {
mlibc::infoLogger() << "mlibc: pthread_join() called on a detached thread" << frg::endlog;
return EINVAL;
}
if (!__atomic_load_n(&tcb->isJoinable, __ATOMIC_ACQUIRE))
return EINVAL;
while (!__atomic_load_n(&tcb->didExit, __ATOMIC_ACQUIRE)) {
mlibc::sys_futex_wait(&tcb->didExit, 0, nullptr);
}
if(ret && tcb->returnValueType == TcbThreadReturnValue::Pointer)
*reinterpret_cast<void **>(ret) = tcb->returnValue.voidPtr;
else if(ret && tcb->returnValueType == TcbThreadReturnValue::Integer)
*reinterpret_cast<int *>(ret) = tcb->returnValue.intVal;
// FIXME: destroy tcb here, currently we leak it
return 0;
}
static constexpr size_t default_stacksize = 0x200000;
static constexpr size_t default_guardsize = 4096;
int thread_attr_init(struct __mlibc_threadattr *attr) {
*attr = __mlibc_threadattr{};
attr->__mlibc_stacksize = default_stacksize;
attr->__mlibc_guardsize = default_guardsize;
attr->__mlibc_detachstate = __MLIBC_THREAD_CREATE_JOINABLE;
return 0;
}
static constexpr unsigned int mutexRecursive = 1;
static constexpr unsigned int mutexErrorCheck = 2;
// TODO: either use uint32_t or determine the bit based on sizeof(int).
static constexpr unsigned int mutex_owner_mask = (static_cast<uint32_t>(1) << 30) - 1;
static constexpr unsigned int mutex_waiters_bit = static_cast<uint32_t>(1) << 31;
int thread_mutex_init(struct __mlibc_mutex *__restrict mutex,
const struct __mlibc_mutexattr *__restrict attr) {
auto type = attr ? attr->__mlibc_type : __MLIBC_THREAD_MUTEX_DEFAULT;
auto robust = attr ? attr->__mlibc_robust : __MLIBC_THREAD_MUTEX_STALLED;
auto protocol = attr ? attr->__mlibc_protocol : __MLIBC_THREAD_PRIO_NONE;
auto pshared = attr ? attr->__mlibc_pshared : __MLIBC_THREAD_PROCESS_PRIVATE;
mutex->__mlibc_state = 0;
mutex->__mlibc_recursion = 0;
mutex->__mlibc_flags = 0;
mutex->__mlibc_prioceiling = 0; // TODO: We don't implement this.
if(type == __MLIBC_THREAD_MUTEX_RECURSIVE) {
mutex->__mlibc_flags |= mutexRecursive;
}else if(type == __MLIBC_THREAD_MUTEX_ERRORCHECK) {
mutex->__mlibc_flags |= mutexErrorCheck;
}else{
__ensure(type == __MLIBC_THREAD_MUTEX_NORMAL);
}
// TODO: Other values aren't supported yet.
__ensure(robust == __MLIBC_THREAD_MUTEX_STALLED);
__ensure(protocol == __MLIBC_THREAD_PRIO_NONE);
__ensure(pshared == __MLIBC_THREAD_PROCESS_PRIVATE);
return 0;
}
int thread_mutex_destroy(struct __mlibc_mutex *mutex) {
__ensure(!mutex->__mlibc_state);
return 0;
}
int thread_mutex_lock(struct __mlibc_mutex *mutex) {
unsigned int this_tid = mlibc::this_tid();
unsigned int expected = 0;
while(true) {
if(!expected) {
// Try to take the mutex here.
if(__atomic_compare_exchange_n(&mutex->__mlibc_state,
&expected, this_tid, false, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE)) {
__ensure(!mutex->__mlibc_recursion);
mutex->__mlibc_recursion = 1;
return 0;
}
}else{
// If this (recursive) mutex is already owned by us, increment the recursion level.
if((expected & mutex_owner_mask) == this_tid) {
if(!(mutex->__mlibc_flags & mutexRecursive)) {
if (mutex->__mlibc_flags & mutexErrorCheck)
return EDEADLK;
else
mlibc::panicLogger() << "mlibc: pthread_mutex deadlock detected!"
<< frg::endlog;
}
++mutex->__mlibc_recursion;
return 0;
}
// Wait on the futex if the waiters flag is set.
if(expected & mutex_waiters_bit) {
int e = mlibc::sys_futex_wait((int *)&mutex->__mlibc_state, expected, nullptr);
// If the wait returns EAGAIN, that means that the mutex_waiters_bit was just unset by
// some other thread. In this case, we should loop back around.
// Also do so in case of a signal being caught.
if (e && e != EAGAIN && e != EINTR)
mlibc::panicLogger() << "sys_futex_wait() failed with error code " << e << frg::endlog;
// Opportunistically try to take the lock after we wake up.
expected = 0;
}else{
// Otherwise we have to set the waiters flag first.
unsigned int desired = expected | mutex_waiters_bit;
if(__atomic_compare_exchange_n((int *)&mutex->__mlibc_state,
reinterpret_cast<int*>(&expected), desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
expected = desired;
}
}
}
}
int thread_mutex_unlock(struct __mlibc_mutex *mutex) {
// Decrement the recursion level and unlock if we hit zero.
__ensure(mutex->__mlibc_recursion);
if(--mutex->__mlibc_recursion)
return 0;
auto flags = mutex->__mlibc_flags;
// Reset the mutex to the unlocked state.
auto state = __atomic_exchange_n(&mutex->__mlibc_state, 0, __ATOMIC_RELEASE);
// After this point the mutex is unlocked, and therefore we cannot access its contents as it
// may have been destroyed by another thread.
unsigned int this_tid = mlibc::this_tid();
if ((flags & mutexErrorCheck) && (state & mutex_owner_mask) != this_tid)
return EPERM;
if ((flags & mutexErrorCheck) && !(state & mutex_owner_mask))
return EINVAL;
__ensure((state & mutex_owner_mask) == this_tid);
if(state & mutex_waiters_bit) {
// Wake the futex if there were waiters. Since the mutex might not exist at this location
// anymore, we must conservatively ignore EACCES and EINVAL which may occur as a result.
int e = mlibc::sys_futex_wake((int *)&mutex->__mlibc_state);
__ensure(e >= 0 || e == EACCES || e == EINVAL);
}
return 0;
}
int thread_mutexattr_init(struct __mlibc_mutexattr *attr) {
attr->__mlibc_type = __MLIBC_THREAD_MUTEX_DEFAULT;
attr->__mlibc_robust = __MLIBC_THREAD_MUTEX_STALLED;
attr->__mlibc_pshared = __MLIBC_THREAD_PROCESS_PRIVATE;
attr->__mlibc_protocol = __MLIBC_THREAD_PRIO_NONE;
return 0;
}
int thread_mutexattr_destroy(struct __mlibc_mutexattr *attr) {
memset(attr, 0, sizeof(*attr));
return 0;
}
int thread_mutexattr_gettype(const struct __mlibc_mutexattr *__restrict attr, int *__restrict type) {
*type = attr->__mlibc_type;
return 0;
}
int thread_mutexattr_settype(struct __mlibc_mutexattr *attr, int type) {
if (type != __MLIBC_THREAD_MUTEX_NORMAL && type != __MLIBC_THREAD_MUTEX_ERRORCHECK
&& type != __MLIBC_THREAD_MUTEX_RECURSIVE)
return EINVAL;
attr->__mlibc_type = type;
return 0;
}
int thread_cond_init(struct __mlibc_cond *__restrict cond, const struct __mlibc_condattr *__restrict attr) {
auto clock = attr ? attr->__mlibc_clock : CLOCK_REALTIME;
auto pshared = attr ? attr->__mlibc_pshared : __MLIBC_THREAD_PROCESS_PRIVATE;
cond->__mlibc_clock = clock;
cond->__mlibc_flags = pshared;
__atomic_store_n(&cond->__mlibc_seq, 1, __ATOMIC_RELAXED);
return 0;
}
int thread_cond_destroy(struct __mlibc_cond *) {
return 0;
}
int thread_cond_broadcast(struct __mlibc_cond *cond) {
__atomic_fetch_add(&cond->__mlibc_seq, 1, __ATOMIC_RELEASE);
if(int e = mlibc::sys_futex_wake((int *)&cond->__mlibc_seq); e)
__ensure(!"sys_futex_wake() failed");
return 0;
}
int thread_cond_timedwait(struct __mlibc_cond *__restrict cond, __mlibc_mutex *__restrict mutex,
const struct timespec *__restrict abstime) {
// TODO: pshared isn't supported yet.
__ensure(cond->__mlibc_flags == 0);
constexpr long nanos_per_second = 1'000'000'000;
if (abstime && (abstime->tv_nsec < 0 || abstime->tv_nsec >= nanos_per_second))
return EINVAL;
auto seq = __atomic_load_n(&cond->__mlibc_seq, __ATOMIC_ACQUIRE);
// TODO: handle locking errors and cancellation properly.
while (true) {
if (thread_mutex_unlock(mutex))
__ensure(!"Failed to unlock the mutex");
int e;
if (abstime) {
// Adjust for the fact that sys_futex_wait accepts a *timeout*, but
// pthread_cond_timedwait accepts an *absolute time*.
// Note: mlibc::sys_clock_get is available unconditionally.
struct timespec now;
if (mlibc::sys_clock_get(cond->__mlibc_clock, &now.tv_sec, &now.tv_nsec))
__ensure(!"sys_clock_get() failed");
struct timespec timeout;
timeout.tv_sec = abstime->tv_sec - now.tv_sec;
timeout.tv_nsec = abstime->tv_nsec - now.tv_nsec;
// Check if abstime has already passed.
if (timeout.tv_sec < 0 || (timeout.tv_sec == 0 && timeout.tv_nsec < 0)) {
if (thread_mutex_lock(mutex))
__ensure(!"Failed to lock the mutex");
return ETIMEDOUT;
} else if (timeout.tv_nsec >= nanos_per_second) {
timeout.tv_nsec -= nanos_per_second;
timeout.tv_sec++;
__ensure(timeout.tv_nsec < nanos_per_second);
} else if (timeout.tv_nsec < 0) {
timeout.tv_nsec += nanos_per_second;
timeout.tv_sec--;
__ensure(timeout.tv_nsec >= 0);
}
e = mlibc::sys_futex_wait((int *)&cond->__mlibc_seq, seq, &timeout);
} else {
e = mlibc::sys_futex_wait((int *)&cond->__mlibc_seq, seq, nullptr);
}
if (thread_mutex_lock(mutex))
__ensure(!"Failed to lock the mutex");
// There are four cases to handle:
// 1. e == 0: this indicates a (potentially spurious) wakeup. The value of
// seq *must* be checked to distinguish these two cases.
// 2. e == EAGAIN: this indicates that the value of seq changed before we
// went to sleep. We don't need to check seq in this case.
// 3. e == EINTR: a signal was delivered. The man page allows us to choose
// whether to go to sleep again or to return 0, but we do the former
// to match other libcs.
// 4. e == ETIMEDOUT: this should only happen if abstime is set.
if (e == 0) {
auto cur_seq = __atomic_load_n(&cond->__mlibc_seq, __ATOMIC_ACQUIRE);
if (cur_seq > seq)
return 0;
} else if (e == EAGAIN) {
__ensure(__atomic_load_n(&cond->__mlibc_seq, __ATOMIC_ACQUIRE) > seq);
return 0;
} else if (e == EINTR) {
continue;
} else if (e == ETIMEDOUT) {
__ensure(abstime);
return ETIMEDOUT;
} else {
mlibc::panicLogger() << "sys_futex_wait() failed with error " << e << frg::endlog;
}
}
}
} // namespace mlibc
@@ -0,0 +1,282 @@
#include <limits.h>
#include <mlibc/debug.hpp>
#define FMT(obj) format_object((obj), opts, formatter)
#define LOG_NAME_LOC(name, loc) "ubsan: " name " at " << loc << "\n "
#define LOG_LHS_RHS(lhs, rhs) "LHS = " << (lhs) << ", RHS = " << (rhs)
struct SourceLocation {
const char *filename;
uint32_t line;
uint32_t column;
};
template<class F>
void format_object(const SourceLocation &loc, frg::format_options opts, F &formatter) {
FMT(loc.filename);
FMT(":");
FMT(loc.line);
FMT(":");
FMT(loc.column);
}
using ValueHandle = uintptr_t;
struct TypeDescriptor {
enum class Kind : uint16_t {
Integer = 0x0000,
Float = 0x0001,
Unknown = 0xffff
} kind;
uint16_t info;
char name[];
unsigned bitWidthInt() const {
return 1 << (info >> 1);
}
bool isInlineInt() const {
if (kind != Kind::Integer)
return false;
auto inlineBits = sizeof(ValueHandle) * CHAR_BIT;
auto valueBits = bitWidthInt();
return inlineBits <= valueBits;
}
bool isSigned() const {
return info & 1;
}
};
template<class F>
void format_object(const TypeDescriptor &type, frg::format_options opts, F &formatter) {
FMT(type.name);
}
struct Value {
const TypeDescriptor &type;
ValueHandle val;
Value(const TypeDescriptor &type, ValueHandle val) : type(type), val(val) {}
};
template<class F>
void format_object(const Value &val, frg::format_options opts, F &formatter) {
if (val.type.isInlineInt() && val.type.isSigned()) {
auto signedValue = static_cast<int64_t>(val.val);
FMT(signedValue);
} else if (val.type.isInlineInt() && !val.type.isSigned()) {
auto unsignedValue = static_cast<uint64_t>(val.val);
FMT(unsignedValue);
}
FMT(" (");
FMT(val.type);
FMT(")");
}
// --- Hook implementations ---
struct TypeMismatch {
SourceLocation loc;
const TypeDescriptor &type;
unsigned char logAlignment;
unsigned char kind;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_type_mismatch_v1(TypeMismatch *tm, ValueHandle pointer) {
// TODO: Make this print more information.
mlibc::panicLogger()
<< LOG_NAME_LOC("type mismatch", tm->loc)
<< "accessed address " << (void *)pointer << " but type "
<< tm->type << " requires alignment " << (1 << tm->logAlignment)
<< frg::endlog;
}
struct PointerOverflowData {
SourceLocation loc;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_pointer_overflow(PointerOverflowData *pod, ValueHandle base, ValueHandle result) {
(void)base;
(void)result;
mlibc::panicLogger()
<< LOG_NAME_LOC("pointer overflow", pod->loc)
<< frg::endlog;
}
struct InvalidValueData {
SourceLocation loc;
const TypeDescriptor &type;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_load_invalid_value(InvalidValueData *ivd, ValueHandle value) {
(void)value;
mlibc::panicLogger()
<< LOG_NAME_LOC("load of invalid value", ivd->loc)
<< frg::endlog;
}
struct OverflowData {
SourceLocation loc;
const TypeDescriptor &type;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_add_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
mlibc::panicLogger()
<< LOG_NAME_LOC("add overflowed ", od->loc)
<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
<< frg::endlog;
}
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_sub_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
mlibc::panicLogger()
<< LOG_NAME_LOC("sub overflowed", od->loc)
<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
<< frg::endlog;
}
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_mul_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
mlibc::panicLogger()
<< LOG_NAME_LOC("mul overflowed", od->loc)
<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
<< frg::endlog;
}
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_divrem_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
mlibc::panicLogger()
<< LOG_NAME_LOC("divrem overflowed", od->loc)
<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
<< frg::endlog;
}
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_negate_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
mlibc::panicLogger()
<< LOG_NAME_LOC("negate overflowed", od->loc)
<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
<< frg::endlog;
}
struct ShiftOutOfBoundsData {
SourceLocation loc;
const TypeDescriptor &lhsType;
const TypeDescriptor &rhsType;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_shift_out_of_bounds(ShiftOutOfBoundsData *soob, ValueHandle lhs, ValueHandle rhs) {
mlibc::panicLogger()
<< LOG_NAME_LOC("shift out of bounds", soob->loc)
<< LOG_LHS_RHS(Value(soob->lhsType, lhs), Value(soob->rhsType, rhs))
<< frg::endlog;
}
struct OutOfBoundsData {
SourceLocation loc;
const TypeDescriptor &arrayType;
const TypeDescriptor &indexType;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_out_of_bounds(OutOfBoundsData *oobd, ValueHandle data) {
(void)data;
mlibc::panicLogger()
<< LOG_NAME_LOC("out of bounds access", oobd->loc)
<< frg::endlog;
}
struct UnreachableData {
SourceLocation loc;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_builtin_unreachable(UnreachableData *ubd) {
mlibc::panicLogger()
<< LOG_NAME_LOC("reached __builtin_unreachable()", ubd->loc)
<< frg::endlog;
}
struct InvalidBuiltinData {
SourceLocation loc;
unsigned char kind;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_invalid_builtin(InvalidBuiltinData *ibd) {
mlibc::panicLogger()
<< LOG_NAME_LOC("reached invalid builtin", ibd->loc)
<< frg::endlog;
}
struct VLABoundData {
SourceLocation loc;
const TypeDescriptor &type;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_vla_bound_not_positive(VLABoundData *vlabd) {
mlibc::panicLogger()
<< LOG_NAME_LOC("VLA bound not positive", vlabd->loc)
<< frg::endlog;
}
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_missing_return(UnreachableData *data) {
mlibc::panicLogger()
<< LOG_NAME_LOC("reached end of a value-returning function without returning a value", data->loc)
<< frg::endlog;
}
struct NonNullArgData {
SourceLocation loc;
SourceLocation attr_loc;
int arg_index;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_nonnull_arg(NonNullArgData *data) {
mlibc::panicLogger()
<< LOG_NAME_LOC("null pointer passed to non-null argument", data->loc)
<< "argument " << data->arg_index << " is required to be non-null in "
<< data->attr_loc << frg::endlog;
}
struct FloatCastOverflowData {
SourceLocation loc;
const TypeDescriptor &from_type;
const TypeDescriptor &to_type;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_float_cast_overflow(FloatCastOverflowData *data, ValueHandle from) {
(void) from;
mlibc::panicLogger()
<< LOG_NAME_LOC("float cast overflow", data->loc)
<< "from " << data->from_type << " to "
<< data->to_type << frg::endlog;
}
struct FunctionTypeMismatchData {
SourceLocation loc;
const TypeDescriptor &type;
};
extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_function_type_mismatch(FunctionTypeMismatchData *data, ValueHandle from) {
(void) from;
mlibc::panicLogger()
<< LOG_NAME_LOC("function type mismatch", data->loc)
<< frg::endlog;
}