#include <fenv.h>
#include <float.h>
#include <math.h>
#include <stdlib.h>
#include "semq_internal.h"
#include "semq_selftest_vectors.h"
#include "semq_sha256.h"
static const uint8_t SPARSE_TO_DENSE[30] = {
0, 10, 11, 12, 13, 14, 15, 16, 17, 18,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
};
static const uint8_t DENSE_TO_SPARSE[19] = {
10, 21, 22, 23, 24, 25, 26, 27, 28, 29, 1, 2, 3, 4, 5, 6, 7, 8, 9,
};
static semq_status_t kernel_encode(const semq_codec_t* c, const float* row, uint8_t* out);
static void codec_init(semq_codec_t* c, const semq_config_t* cfg, semq_backend_kind_t kind) {
c->config = *cfg;
c->bpv = semq_config_bytes_per_vector(cfg);
c->units = semq_config_units_per_row(cfg);
c->phase_packed = (cfg->op == SEMQ_PHASE && cfg->p1 <= 16u) ? 1u : 0u;
c->max_magnitude = semq_config_max_magnitude(cfg);
c->kind = kind;
c->orbit = semq_dispatch_get(kind);
c->phase = semq_dispatch_get_phase(kind);
c->quant = semq_dispatch_get_quant(kind);
if (cfg->op == SEMQ_PHASE) {
for (uint32_t sct = 0u; sct < cfg->p1; sct++) {
double rep[2];
semq_phase_representative(cfg->p1, sct, rep);
c->phase_rep[2u * sct + 0u] = (float)rep[0];
c->phase_rep[2u * sct + 1u] = (float)rep[1];
}
}
}
#ifdef SEMQ_TEST_ALLOCATOR
static uint32_t corrupt_self_test;
void semqi_test_corrupt_self_test(uint32_t on) { corrupt_self_test = on; }
#endif
static semq_status_t self_test(semq_backend_kind_t kind, uint32_t op, semq_error_t* err) {
static const uint8_t ABC_SHA256[32] = {
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
};
uint8_t digest[32];
semq_sha256_ctx_t h;
semq_sha256_init(&h);
semq_sha256_update(&h, (const uint8_t*)"abc", 3u);
semq_sha256_final(&h, digest);
if (memcmp(digest, ABC_SHA256, sizeof(digest)) != 0) {
return semqi_fail(err, SEMQ_ERR_INTERNAL, SEMQ_NONE, SEMQ_NONE,
"self-test failed: SHA-256 gives a wrong digest on this build");
}
for (size_t k = 0u; k < sizeof(SEMQI_KAT) / sizeof(SEMQI_KAT[0]); k++) {
const semqi_kat_t* kat = &SEMQI_KAT[k];
if (kat->op != op) continue;
semq_config_t cfg;
semq_status_t s = (op == SEMQ_ORBIT) ? semq_config_orbit(kat->dim, kat->p1, &cfg, NULL)
: (op == SEMQ_PHASE) ? semq_config_phase(kat->dim, kat->p1, &cfg, NULL)
: semq_config_quant(kat->dim, kat->p1, &cfg, NULL);
if (s != SEMQ_OK || kat->dim > 64u || semq_config_bytes_per_vector(&cfg) != kat->bytes_per_vector) {
return semqi_fail(err, SEMQ_ERR_INTERNAL, SEMQ_NONE, SEMQ_NONE,
"self-test failed: a known-answer config does not match this core");
}
semq_codec_t t;
memset(&t, 0, sizeof(t));
codec_init(&t, &cfg, kind);
for (uint32_t r = 0u; r < SEMQI_KAT_ROWS; r++) {
float row[64];
uint8_t got[64];
for (uint32_t i = 0u; i < kat->dim; i++) {
const uint32_t bits = kat->input[(size_t)r * kat->dim + i];
memcpy(&row[i], &bits, sizeof(bits));
}
s = kernel_encode(&t, row, got);
const uint8_t* want = kat->expected + (size_t)r * kat->bytes_per_vector;
int differs = s != SEMQ_OK || memcmp(got, want, kat->bytes_per_vector) != 0;
#ifdef SEMQ_TEST_ALLOCATOR
differs |= corrupt_self_test != 0u;
#endif
if (differs) {
return semqi_fail(err, SEMQ_ERR_INTERNAL, SEMQ_NONE, SEMQ_NONE,
"self-test failed: this build's kernel does not produce the "
"reference bytes; the core is miscompiled or the CPU is faulty");
}
}
}
return SEMQ_OK;
}
SEMQ_API semq_status_t semq_codec_create(const semq_config_t* cfg, semq_codec_t** out,
semq_error_t* err) {
if (out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "output is NULL");
*out = NULL;
semq_status_t s = semq_config_validate(cfg, err);
if (s != SEMQ_OK) return s;
if (fegetround() != FE_TONEAREST) {
return semqi_fail(err, SEMQ_ERR_UNSUPPORTED, SEMQ_NONE, SEMQ_NONE,
"floating-point rounding mode is not round-to-nearest");
}
const semq_backend_kind_t kind = semq_dispatch_select_for(cfg->op);
s = self_test(kind, cfg->op, err);
if (s != SEMQ_OK) return s;
semq_codec_t* c = (semq_codec_t*)semqi_calloc(1u, sizeof(*c));
if (c == NULL) return SEMQI_NOMEM(err);
codec_init(c, cfg, kind);
*out = c;
semqi_ok(err);
return SEMQ_OK;
}
SEMQ_API void semq_codec_free(semq_codec_t* codec) {
if (codec == NULL) return;
memset(codec, 0, sizeof(*codec));
semqi_free(codec);
}
SEMQ_API const semq_config_t* semq_codec_config(const semq_codec_t* codec) {
return codec == NULL ? NULL : &codec->config;
}
SEMQ_API const char* semq_codec_backend(const semq_codec_t* codec) {
if (codec == NULL) return "none";
return semqi_backend_name_for(codec->kind, codec->config.op);
}
static semq_status_t admit_row(const float* src, uint32_t dim, float* scratch, uint64_t row,
semq_error_t* err) {
for (uint32_t i = 0u; i < dim; i++) {
const float x = src[i];
scratch[i] = (fabsf(x) < FLT_MIN) ? 0.0f : x;
}
for (uint32_t i = 0u; i < dim; i++) {
if (!isfinite(scratch[i])) {
return SEMQI_INVALID(err, row, i, "vector has a NaN or infinite coordinate");
}
}
double s = 0.0;
for (uint32_t i = 0u; i < dim; i++) {
const double x = (double)scratch[i];
s += x * x;
}
const double dev = s - 1.0;
if (!(dev <= 0.0009765625 && dev >= -0.0009765625)) {
return SEMQI_INVALID(err, row, SEMQ_NONE,
"vector is not unit-norm (|sum of squares - 1| > 2^-10)");
}
return SEMQ_OK;
}
static semq_status_t kernel_encode(const semq_codec_t* c, const float* row, uint8_t* out) {
switch (c->config.op) {
case SEMQ_ORBIT: {
const semq_status_t s = c->orbit->encode(row, c->config.dim, c->config.p1, out);
if (s != SEMQ_OK) return s;
for (uint32_t i = 0u; i < c->config.dim; i++) out[i] = SPARSE_TO_DENSE[out[i]];
return SEMQ_OK;
}
case SEMQ_PHASE:
return c->phase->encode(row, c->config.dim, c->config.p1, c->phase_packed, out);
default:
return c->quant->encode(row, c->config.dim, c->config.p1, c->max_magnitude, out);
}
}
SEMQ_API semq_status_t semq_codec_encode(const semq_codec_t* codec, const semq_ids_t* ids,
const float* vectors, const semq_pair_t* manifest,
uint32_t n_pairs, semq_encoding_t** out,
semq_error_t* err) {
if (out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "output is NULL");
*out = NULL;
if (codec == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "codec is NULL");
if (ids == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "ids is NULL");
if (ids->n > 0u && vectors == NULL) {
return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "vectors is NULL");
}
if (fegetround() != FE_TONEAREST) {
return semqi_fail(err, SEMQ_ERR_UNSUPPORTED, SEMQ_NONE, SEMQ_NONE,
"floating-point rounding mode is not round-to-nearest");
}
uint64_t input_bytes;
if (semqi_mul_ovf(ids->n, (uint64_t)codec->config.dim * sizeof(float), &input_bytes) ||
semqi_too_big(input_bytes)) return SEMQI_NOMEM(err);
uint64_t* perm = NULL;
semq_status_t s = semqi_sort_ids(ids, &perm, err);
if (s != SEMQ_OK) return s;
const uint32_t dim = codec->config.dim;
uint64_t rows_len;
if (semqi_mul_ovf(ids->n, codec->bpv, &rows_len) || semqi_too_big(rows_len)) {
semqi_free(perm);
return SEMQI_NOMEM(err);
}
uint8_t* rows = (uint8_t*)semqi_alloc(rows_len);
float* scratch = (float*)semqi_alloc((uint64_t)dim * sizeof(float));
if (rows == NULL || scratch == NULL) {
semqi_free(perm); semqi_free(rows); semqi_free(scratch);
return SEMQI_NOMEM(err);
}
for (uint64_t i = 0u; i < ids->n; i++) {
const uint64_t src_row = perm[i];
s = admit_row(vectors + (size_t)src_row * dim, dim, scratch, src_row, err);
if (s != SEMQ_OK) goto fail;
s = kernel_encode(codec, scratch, rows + (size_t)i * codec->bpv);
if (s != SEMQ_OK) {
s = semqi_fail(err, SEMQ_ERR_INTERNAL, src_row, SEMQ_NONE, "kernel rejected an admitted row");
goto fail;
}
}
{
semq_encoding_t* enc = semqi_encoding_alloc();
if (enc == NULL) { s = SEMQI_NOMEM(err); goto fail; }
enc->config = codec->config;
enc->bpv = codec->bpv;
enc->units = codec->units;
enc->id_kind = ids->kind;
enc->n = ids->n;
enc->rows = rows;
enc->rows_len = rows_len;
rows = NULL;
s = semqi_ids_section(ids, perm, &enc->ids, &enc->ids_len, err);
if (s == SEMQ_OK) s = semqi_manifest_build(manifest, n_pairs, &enc->manifest, &enc->manifest_len, err);
if (s == SEMQ_OK) s = semqi_encoding_finish(enc, err);
if (s != SEMQ_OK) { semq_encoding_free(enc); goto fail; }
*out = enc;
}
semqi_free(perm);
semqi_free(scratch);
semqi_ok(err);
return SEMQ_OK;
fail:
semqi_free(perm);
semqi_free(rows);
semqi_free(scratch);
return s;
}
static semq_status_t check_pair(const semq_codec_t* codec, const semq_encoding_t* enc,
const void* out, semq_error_t* err) {
if (codec == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "codec is NULL");
if (enc == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "encoding is NULL");
if (enc->n > 0u && out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "output is NULL");
if (!semq_config_equal(&codec->config, &enc->config)) {
return semqi_fail(err, SEMQ_ERR_INCOMPATIBLE, SEMQ_NONE, SEMQ_NONE,
"encoding config differs from codec config");
}
return SEMQ_OK;
}
SEMQ_API semq_status_t semq_codec_decode(const semq_codec_t* codec, const semq_encoding_t* enc,
float* out, semq_error_t* err) {
semq_status_t s = check_pair(codec, enc, out, err);
if (s != SEMQ_OK) return s;
const uint32_t dim = codec->config.dim;
if (codec->config.op == SEMQ_PHASE) {
const uint32_t pairs = dim / 2u;
for (uint64_t i = 0u; i < enc->n; i++) {
const uint8_t* row = enc->rows + (size_t)i * codec->bpv;
float* dst = out + (size_t)i * dim;
for (uint32_t p = 0u; p < pairs; p++) {
const uint32_t sct = codec->phase_packed
? (((uint32_t)row[p >> 1] >> (4u * (p & 1u))) & 0x0Fu)
: (uint32_t)row[p];
dst[2u * p + 0u] = codec->phase_rep[2u * sct + 0u];
dst[2u * p + 1u] = codec->phase_rep[2u * sct + 1u];
}
}
semqi_ok(err);
return SEMQ_OK;
}
double* tmp = (double*)semqi_alloc((uint64_t)dim * sizeof(double));
uint8_t* sparse = (uint8_t*)semqi_alloc(codec->config.op == SEMQ_ORBIT ? dim : 1u);
if (tmp == NULL || sparse == NULL) { semqi_free(tmp); semqi_free(sparse); return SEMQI_NOMEM(err); }
for (uint64_t i = 0u; i < enc->n; i++) {
const uint8_t* row = enc->rows + (size_t)i * codec->bpv;
semq_status_t k;
switch (codec->config.op) {
case SEMQ_ORBIT:
for (uint32_t j = 0u; j < dim; j++) sparse[j] = DENSE_TO_SPARSE[row[j]];
k = codec->orbit->reconstruct(sparse, dim, codec->config.p1, tmp);
break;
default:
k = codec->quant->reconstruct(row, dim, codec->config.p1, codec->max_magnitude, tmp);
break;
}
if (k != SEMQ_OK) {
semqi_free(tmp); semqi_free(sparse);
return semqi_fail(err, SEMQ_ERR_INTERNAL, i, SEMQ_NONE, "kernel rejected a canonical row");
}
float* dst = out + (size_t)i * dim;
for (uint32_t j = 0u; j < dim; j++) dst[j] = (float)tmp[j];
}
semqi_free(tmp);
semqi_free(sparse);
semqi_ok(err);
return SEMQ_OK;
}
SEMQ_API semq_status_t semq_codec_unpack(const semq_codec_t* codec, const semq_encoding_t* enc,
uint8_t* out, semq_error_t* err) {
semq_status_t s = check_pair(codec, enc, out, err);
if (s != SEMQ_OK) return s;
for (uint64_t i = 0u; i < enc->n; i++) {
semqi_unpack_row(&codec->config, enc->rows + (size_t)i * codec->bpv,
out + (size_t)i * codec->units);
}
semqi_ok(err);
return SEMQ_OK;
}