llm
Download
158 lines 5.8 KiBCModified 2026-08-31 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
// Metamethods are methods that all types share for hashing, equality, comparison, and textifying

#include <gc.h>
#include <setjmp.h>
#include <stdint.h>
#include <string.h>

#include "datatypes/typeinfo.h"
#include "fail.h"
#include "list.h"
#include "metamethods.h"
#include "siphash.h"
#include "table.h"
#include "text.h"
#include "util.h"

PUREFUNC public uint64_t generic_hash(const void *obj, const TypeInfo_t *type) {
    if (type->metamethods.hash) return type->metamethods.hash(obj, type);

    return siphash24((void *)obj, (size_t)(type->size));
}

PUREFUNC public int32_t generic_compare(const void *x, const void *y, const TypeInfo_t *type) {
    if (x == y) return 0;

    if (type->metamethods.compare) return type->metamethods.compare(x, y, type);

    return (int32_t)memcmp((void *)x, (void *)y, (size_t)(type->size));
}

PUREFUNC public bool generic_equal(const void *x, const void *y, const TypeInfo_t *type) {
    if (x == y) return true;

    if (type->metamethods.equal) return type->metamethods.equal(x, y, type);

    return (generic_compare(x, y, type) == 0);
}

public
Text_t generic_as_text(const void *obj, bool colorize, const TypeInfo_t *type) {
    if (!type->metamethods.as_text) fail_text(Text("No text metamethod provided for type!"));

    return type->metamethods.as_text(obj, colorize, type);
}

public
void _serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) {
    if (type->metamethods.serialize) return type->metamethods.serialize(obj, out, pointers, type);

    fwrite(obj, (size_t)type->size, 1, out);
}

public
List_t generic_serialize(const void *x, const TypeInfo_t *type) {
    char *buf = NULL;
    size_t size = 0;
    FILE *stream = open_memstream(&buf, &size);
    Table_t pointers = EMPTY_TABLE;
    _serialize(x, stream, &pointers, type);
    fclose(stream);
    List_t bytes = {
        .data = GC_MALLOC_ATOMIC(size),
        .length = (uint64_t)size,
        .stride = 1,
        .atomic = 1,
    };
    memcpy(bytes.data, buf, size);
    free(buf);
    return bytes;
}

// Deserializers recurse into their nested values, and some encodings let one
// level use as few as two bytes (e.g. an empty table plus a "has a fallback" flag),
// so a small hostile input can otherwise nest deeply enough to cause a stack
// overflow. deserialization_failed() exists to turn that into `none` return value.
// Cap the nesting well below the shallowest stack Tomo runs on (a secondary thread
// on macOS gets 512KB). Data this deeply nested can't be produced by _serialize()
// either, which recurses the same way.
#define MAX_DESERIALIZATION_DEPTH 1000
static _Thread_local int deserialization_depth = 0;

public
void _deserialize(FILE *input, void *outval, List_t *pointers, const TypeInfo_t *type) {
    if (deserialization_depth >= MAX_DESERIALIZATION_DEPTH) deserialization_failed();
    deserialization_depth += 1;

    if (type->metamethods.deserialize) type->metamethods.deserialize(input, outval, pointers, type);
    else if (fread(outval, (size_t)type->size, 1, input) != 1) deserialization_failed();

    deserialization_depth -= 1;
}

// Where to jump when a deserializer discovers that its input isn't a
// well-formed encoding of the type being read. This is set for the duration of
// a `generic_deserialize()` call; outside of one (i.e. if a deserializer is
// invoked directly), a malformed input is still a hard failure.
static _Thread_local jmp_buf *deserialization_failure_handler = NULL;

public
_Noreturn void deserialization_failed(void) {
    if (deserialization_failure_handler) longjmp(*deserialization_failure_handler, 1);
    fail_text(Text("This data could not be deserialized"));
}

// How many bytes are left to be read. Deserializers use this to sanity-check
// length prefixes *before* allocating, so that corrupt data can't ask for an
// enormous allocation.
public
int64_t deserialization_bytes_remaining(FILE *in) {
    long pos = ftell(in);
    if (pos < 0 || fseek(in, 0, SEEK_END) != 0) return INT64_MAX;
    long end = ftell(in);
    if (end < 0 || fseek(in, pos, SEEK_SET) != 0) return INT64_MAX;
    return (int64_t)(end - pos);
}

public
bool generic_deserialize(List_t bytes, void *outval, const TypeInfo_t *type) {
    if (bytes.stride != 1) List$compact(&bytes, 1);

    FILE *input = fmemopen(bytes.data, (size_t)bytes.length, "r");
    if (input == NULL) return false;

    List_t pointers = EMPTY_LIST;
    jmp_buf *prev_handler = deserialization_failure_handler;
    int prev_depth = deserialization_depth;
    jmp_buf on_failure;
    bool success;
    if (setjmp(on_failure) == 0) {
        deserialization_failure_handler = &on_failure;
        _deserialize(input, outval, &pointers, type);
        // Leftover bytes mean this isn't an encoding of this type (or is an
        // encoding of something bigger), so don't call it a success:
        success = (fgetc(input) == EOF);
    } else {
        success = false;
    }
    // A longjmp out of a nested deserializer skips every pending decrement, so
    // restore the counter rather than leaving it stuck high for the next call:
    deserialization_depth = prev_depth;
    deserialization_failure_handler = prev_handler;
    fclose(input);
    return success;
}

__attribute__((noreturn)) public
void cannot_serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *type) {
    (void)obj, (void)out, (void)pointers;
    Text_t type_text = generic_as_text(NULL, false, type);
    fail_text(Text$concat(Text("Values of type "), type_text, Text(" cannot be serialized or deserialized!")));
}

__attribute__((noreturn)) public
void cannot_deserialize(FILE *in, void *obj, List_t *pointers, const TypeInfo_t *type) {
    (void)obj, (void)in, (void)pointers;
    Text_t type_text = generic_as_text(NULL, false, type);
    fail_text(Text$concat(Text("Values of type "), type_text, Text(" cannot be serialized or deserialized!")));
}