mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-06 17:49:47 +00:00
664 lines
28 KiB
C
664 lines
28 KiB
C
#include "solver.h"
|
|
#include "stats.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
enum { SOLVER_DEFAULT_NODES = 1000000, SOLVER_DEFAULT_EDGES = 4000000 };
|
|
#define SOLVER_DEFAULT_STEPS UINT64_C(16000000)
|
|
|
|
typedef enum {
|
|
SOLVER_LEAF,
|
|
SOLVER_COPY,
|
|
SOLVER_JOIN,
|
|
SOLVER_SELECT,
|
|
SOLVER_TYPE
|
|
} solver_operation_t;
|
|
typedef struct {
|
|
cbm_resolve_solver_result_t result;
|
|
solver_operation_t operation;
|
|
cbm_resolve_solver_status_t leaf_status;
|
|
cbm_resolve_solver_value_t leaf_value;
|
|
const cbm_resolve_proof_t *audit;
|
|
size_t *dependency_indices;
|
|
size_t remaining;
|
|
size_t reverse_head;
|
|
const char *selector, *prefix, *suffix;
|
|
bool selected, select_type;
|
|
CBMTypeKind wrapper;
|
|
uint32_t version;
|
|
} solver_node_t;
|
|
typedef struct {
|
|
size_t dependent;
|
|
size_t next;
|
|
} solver_reverse_edge_t;
|
|
|
|
struct cbm_resolve_solver {
|
|
CBMArena *arena;
|
|
cbm_resolve_proof_pool_t *pool;
|
|
cbm_resolve_solver_limits_t limits;
|
|
solver_node_t **nodes;
|
|
size_t count, capacity, edge_count, selections;
|
|
cbm_resolve_solver_status_t failure;
|
|
bool solved;
|
|
uint64_t steps;
|
|
};
|
|
|
|
static void solver_fail(cbm_resolve_solver_t *solver, cbm_resolve_solver_status_t status) {
|
|
if (solver && (solver->failure == CBM_SOLVER_PENDING || status == CBM_SOLVER_FAILED))
|
|
solver->failure = status;
|
|
}
|
|
|
|
cbm_resolve_solver_t *cbm_resolve_solver_new(CBMArena *arena, cbm_resolve_proof_pool_t *pool,
|
|
cbm_resolve_solver_limits_t limits) {
|
|
if (!arena || !pool)
|
|
return NULL;
|
|
cbm_resolve_solver_t *solver = cbm_arena_calloc(arena, sizeof(*solver));
|
|
if (!solver)
|
|
return NULL;
|
|
solver->arena = arena;
|
|
solver->pool = pool;
|
|
solver->limits =
|
|
(cbm_resolve_solver_limits_t){.nodes = limits.nodes ? limits.nodes : SOLVER_DEFAULT_NODES,
|
|
.edges = limits.edges ? limits.edges : SOLVER_DEFAULT_EDGES,
|
|
.steps = limits.steps ? limits.steps : SOLVER_DEFAULT_STEPS};
|
|
return solver;
|
|
}
|
|
|
|
static int compare_strings(const void *left, const void *right) {
|
|
return strcmp(*(const char *const *)left, *(const char *const *)right);
|
|
}
|
|
|
|
static bool reserve_node(cbm_resolve_solver_t *solver) {
|
|
if (solver->count < solver->capacity)
|
|
return true;
|
|
size_t capacity = solver->capacity ? solver->capacity * 2 : 32;
|
|
if (capacity < solver->capacity || capacity > SIZE_MAX / sizeof(*solver->nodes))
|
|
return false;
|
|
solver_node_t **nodes = cbm_arena_alloc(solver->arena, capacity * sizeof(*nodes));
|
|
if (!nodes)
|
|
return false;
|
|
if (solver->count)
|
|
memcpy(nodes, solver->nodes, solver->count * sizeof(*nodes));
|
|
solver->nodes = nodes;
|
|
solver->capacity = capacity;
|
|
return true;
|
|
}
|
|
|
|
/* Deep copy the bounded shared transfer vocabulary. Unsupported structures
|
|
* remain unknown rather than borrowing a hybrid arena or normalizing away an
|
|
* unknown union arm. The identity preserves each structured wrapper. */
|
|
static const CBMType *owned_type(CBMArena *arena, const CBMType *type, unsigned depth,
|
|
const char **identity) {
|
|
if (!type || depth >= 64)
|
|
return NULL;
|
|
const CBMType *result = NULL;
|
|
const char *inner = NULL;
|
|
if (type->kind == CBM_TYPE_NAMED || type->kind == CBM_TYPE_BUILTIN) {
|
|
const char *name = type->kind == CBM_TYPE_NAMED ? type->data.named.qualified_name
|
|
: type->data.builtin.name;
|
|
if (!name || !*name)
|
|
return NULL;
|
|
char *copy = cbm_arena_strdup(arena, name);
|
|
if (!copy)
|
|
return NULL;
|
|
result = type->kind == CBM_TYPE_NAMED ? cbm_type_named(arena, copy)
|
|
: cbm_type_builtin(arena, copy);
|
|
*identity = cbm_arena_sprintf(arena, "%u:%zu:%s", (unsigned)type->kind, strlen(name), name);
|
|
} else if (type->kind == CBM_TYPE_POINTER || type->kind == CBM_TYPE_SLICE) {
|
|
const CBMType *child =
|
|
type->kind == CBM_TYPE_POINTER ? type->data.pointer.elem : type->data.slice.elem;
|
|
const CBMType *copy = owned_type(arena, child, depth + 1, &inner);
|
|
if (!copy)
|
|
return NULL;
|
|
result = type->kind == CBM_TYPE_POINTER ? cbm_type_pointer(arena, copy)
|
|
: cbm_type_slice(arena, copy);
|
|
*identity = cbm_arena_sprintf(arena, "%u:(%s)", (unsigned)type->kind, inner);
|
|
}
|
|
return result && *identity ? result : NULL;
|
|
}
|
|
|
|
const char *cbm_resolve_solver_type_identity(CBMArena *arena, const CBMType *type) {
|
|
const char *identity = NULL;
|
|
return arena && owned_type(arena, type, 0, &identity) ? identity : NULL;
|
|
}
|
|
|
|
static bool add_node(cbm_resolve_solver_t *solver, const char *key, solver_operation_t operation,
|
|
cbm_resolve_solver_status_t status, cbm_resolve_solver_value_t value,
|
|
const char *const *dependencies, size_t count,
|
|
const cbm_resolve_proof_t *audit) {
|
|
if (!solver || solver->solved || !key || !*key || !audit || (count && !dependencies) ||
|
|
(status == CBM_SOLVER_KNOWN && (!value.target || !*value.target))) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
return false;
|
|
}
|
|
if (solver->failure != CBM_SOLVER_PENDING)
|
|
return solver->failure != CBM_SOLVER_FAILED;
|
|
if (solver->count >= solver->limits.nodes || count > solver->limits.edges ||
|
|
count >= SIZE_MAX / sizeof(const char *)) {
|
|
solver_fail(solver, CBM_SOLVER_LIMITED);
|
|
return true;
|
|
}
|
|
solver_node_t *node = cbm_arena_calloc(solver->arena, sizeof(*node));
|
|
const char **keys = count ? cbm_arena_alloc(solver->arena, count * sizeof(*keys)) : NULL;
|
|
if (!node || (count && !keys) || !reserve_node(solver)) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
return false;
|
|
}
|
|
node->result.key = cbm_arena_strdup(solver->arena, key);
|
|
node->leaf_value = value;
|
|
/* Identities are derived from owned structure, never caller-provided text. */
|
|
node->leaf_value.type_identity = NULL;
|
|
if (value.type) {
|
|
node->leaf_value.type =
|
|
owned_type(solver->arena, value.type, 0, &node->leaf_value.type_identity);
|
|
if (!node->leaf_value.type) {
|
|
node->leaf_value = (cbm_resolve_solver_value_t){0};
|
|
status = CBM_SOLVER_UNSUPPORTED;
|
|
}
|
|
}
|
|
if (value.declaration_identity)
|
|
node->leaf_value.declaration_identity =
|
|
cbm_arena_strdup(solver->arena, value.declaration_identity);
|
|
if (value.target)
|
|
node->leaf_value.target = cbm_arena_strdup(solver->arena, value.target);
|
|
node->audit = cbm_resolve_proof_clone(solver->pool, audit);
|
|
if (!node->result.key || (value.target && !node->leaf_value.target) ||
|
|
(value.declaration_identity && !node->leaf_value.declaration_identity) || !node->audit) {
|
|
solver_fail(solver, solver->pool->failure == CBM_RESOLVE_ANALYSIS_LIMITED
|
|
? CBM_SOLVER_LIMITED
|
|
: CBM_SOLVER_FAILED);
|
|
return solver->failure != CBM_SOLVER_FAILED;
|
|
}
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (!dependencies[i] || !*dependencies[i] ||
|
|
!(keys[i] = cbm_arena_strdup(solver->arena, dependencies[i]))) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
return false;
|
|
}
|
|
}
|
|
if (count > 1)
|
|
qsort(keys, count, sizeof(*keys), compare_strings);
|
|
size_t unique = 0;
|
|
for (size_t i = 0; i < count; i++)
|
|
if (!unique || strcmp(keys[i], keys[unique - 1]))
|
|
keys[unique++] = keys[i];
|
|
if (unique > solver->limits.edges - solver->edge_count) {
|
|
solver_fail(solver, CBM_SOLVER_LIMITED);
|
|
return true;
|
|
}
|
|
node->result.dependencies = keys;
|
|
node->result.dependency_count = unique;
|
|
node->operation = operation;
|
|
node->leaf_status = status;
|
|
node->reverse_head = SIZE_MAX;
|
|
solver->edge_count += unique;
|
|
solver->nodes[solver->count++] = node;
|
|
return true;
|
|
}
|
|
|
|
bool cbm_resolve_solver_leaf(cbm_resolve_solver_t *solver, const char *key,
|
|
cbm_resolve_solver_status_t status, cbm_resolve_solver_value_t value,
|
|
const cbm_resolve_proof_t *audit) {
|
|
if (status <= CBM_SOLVER_PENDING || status > CBM_SOLVER_FAILED) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
return false;
|
|
}
|
|
return add_node(solver, key, SOLVER_LEAF, status, value, NULL, 0, audit);
|
|
}
|
|
|
|
bool cbm_resolve_solver_copy(cbm_resolve_solver_t *solver, const char *key, const char *dependency,
|
|
const cbm_resolve_proof_t *audit) {
|
|
return add_node(solver, key, SOLVER_COPY, CBM_SOLVER_PENDING, (cbm_resolve_solver_value_t){0},
|
|
&dependency, 1, audit);
|
|
}
|
|
|
|
bool cbm_resolve_solver_join(cbm_resolve_solver_t *solver, const char *key,
|
|
const char *const *dependencies, size_t count,
|
|
const cbm_resolve_proof_t *audit) {
|
|
if (!count) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
return false;
|
|
}
|
|
return add_node(solver, key, SOLVER_JOIN, CBM_SOLVER_PENDING, (cbm_resolve_solver_value_t){0},
|
|
dependencies, count, audit);
|
|
}
|
|
|
|
bool cbm_resolve_solver_select(cbm_resolve_solver_t *solver, const char *key, const char *selector,
|
|
const char *prefix, const char *suffix,
|
|
const cbm_resolve_proof_t *audit) {
|
|
if (!prefix || !suffix) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
return false;
|
|
}
|
|
if (!add_node(solver, key, SOLVER_SELECT, CBM_SOLVER_PENDING, (cbm_resolve_solver_value_t){0},
|
|
&selector, 1, audit))
|
|
return false;
|
|
if (solver->failure != CBM_SOLVER_PENDING)
|
|
return solver->failure != CBM_SOLVER_FAILED;
|
|
solver_node_t *node = solver->nodes[solver->count - 1];
|
|
node->selector = node->result.dependencies[0];
|
|
node->prefix = cbm_arena_strdup(solver->arena, prefix);
|
|
node->suffix = cbm_arena_strdup(solver->arena, suffix);
|
|
if (!node->prefix || !node->suffix) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
return false;
|
|
}
|
|
if (solver->selections >= solver->limits.edges - solver->edge_count)
|
|
solver_fail(solver, CBM_SOLVER_LIMITED);
|
|
else
|
|
solver->selections++;
|
|
return true;
|
|
}
|
|
|
|
bool cbm_resolve_solver_select_type(cbm_resolve_solver_t *solver, const char *key,
|
|
const char *selector, const char *prefix, const char *suffix,
|
|
const cbm_resolve_proof_t *audit) {
|
|
bool ok = cbm_resolve_solver_select(solver, key, selector, prefix, suffix, audit);
|
|
if (ok && solver->failure == CBM_SOLVER_PENDING)
|
|
solver->nodes[solver->count - 1]->select_type = true;
|
|
return ok;
|
|
}
|
|
|
|
bool cbm_resolve_solver_type(cbm_resolve_solver_t *solver, const char *key, const char *dependency,
|
|
CBMTypeKind wrapper, uint32_t version,
|
|
const cbm_resolve_proof_t *audit) {
|
|
if (!add_node(solver, key, SOLVER_TYPE, CBM_SOLVER_PENDING, (cbm_resolve_solver_value_t){0},
|
|
&dependency, 1, audit))
|
|
return false;
|
|
if (solver->failure == CBM_SOLVER_PENDING) {
|
|
solver_node_t *node = solver->nodes[solver->count - 1];
|
|
node->wrapper = wrapper;
|
|
node->version = version;
|
|
}
|
|
return solver->failure != CBM_SOLVER_FAILED;
|
|
}
|
|
|
|
static int compare_nodes(const void *left, const void *right) {
|
|
return strcmp((*(solver_node_t *const *)left)->result.key,
|
|
(*(solver_node_t *const *)right)->result.key);
|
|
}
|
|
|
|
static size_t find_node(const cbm_resolve_solver_t *solver, const char *key) {
|
|
size_t low = 0, high = solver->count;
|
|
while (low < high) {
|
|
size_t middle = low + (high - low) / 2;
|
|
cbm_resolve_stats_add(CBM_RESOLVE_STAT_LOOKUP_PROBES, 1);
|
|
int order = strcmp(solver->nodes[middle]->result.key, key);
|
|
if (order < 0)
|
|
low = middle + 1;
|
|
else
|
|
high = middle;
|
|
}
|
|
return low < solver->count && !strcmp(solver->nodes[low]->result.key, key) ? low : SIZE_MAX;
|
|
}
|
|
|
|
static void heap_push(size_t *heap, size_t *count, size_t value) {
|
|
size_t at = (*count)++;
|
|
while (at && heap[(at - 1) / 2] > value) {
|
|
heap[at] = heap[(at - 1) / 2];
|
|
at = (at - 1) / 2;
|
|
}
|
|
heap[at] = value;
|
|
}
|
|
|
|
static size_t heap_pop(size_t *heap, size_t *count) {
|
|
size_t result = heap[0], value = heap[--*count], at = 0;
|
|
while (at < *count / 2) {
|
|
size_t child = at * 2 + 1;
|
|
if (child + 1 < *count && heap[child + 1] < heap[child])
|
|
child++;
|
|
if (heap[child] >= value)
|
|
break;
|
|
heap[at] = heap[child];
|
|
at = child;
|
|
}
|
|
if (*count)
|
|
heap[at] = value;
|
|
return result;
|
|
}
|
|
|
|
static cbm_resolve_solver_status_t analysis_status(cbm_resolve_analysis_t analysis) {
|
|
switch (analysis) {
|
|
case CBM_RESOLVE_ANALYSIS_COMPLETE:
|
|
return CBM_SOLVER_KNOWN;
|
|
case CBM_RESOLVE_ANALYSIS_LIMITED:
|
|
return CBM_SOLVER_LIMITED;
|
|
case CBM_RESOLVE_ANALYSIS_FAILED:
|
|
return CBM_SOLVER_FAILED;
|
|
case CBM_RESOLVE_ANALYSIS_UNSUPPORTED:
|
|
return CBM_SOLVER_UNSUPPORTED;
|
|
default:
|
|
return CBM_SOLVER_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
static cbm_resolve_result_t outcome(const cbm_resolve_solver_result_t *result) {
|
|
cbm_resolve_result_t value = {.reason = "named_dependency_solver"};
|
|
switch (result->status) {
|
|
case CBM_SOLVER_KNOWN:
|
|
value.binding = CBM_RESOLVE_BINDING_UNIQUE;
|
|
value.domain = CBM_RESOLVE_DOMAIN_PROJECT;
|
|
value.analysis = CBM_RESOLVE_ANALYSIS_COMPLETE;
|
|
value.target = result->value.target;
|
|
value.candidate_count = 1;
|
|
break;
|
|
case CBM_SOLVER_AMBIGUOUS:
|
|
value.binding = CBM_RESOLVE_BINDING_AMBIGUOUS;
|
|
value.analysis = CBM_RESOLVE_ANALYSIS_COMPLETE;
|
|
value.candidate_count = 2;
|
|
break;
|
|
case CBM_SOLVER_UNSUPPORTED:
|
|
value.analysis = CBM_RESOLVE_ANALYSIS_UNSUPPORTED;
|
|
break;
|
|
case CBM_SOLVER_LIMITED:
|
|
value.analysis = CBM_RESOLVE_ANALYSIS_LIMITED;
|
|
break;
|
|
case CBM_SOLVER_FAILED:
|
|
value.analysis = CBM_RESOLVE_ANALYSIS_FAILED;
|
|
break;
|
|
case CBM_SOLVER_PENDING:
|
|
value.analysis = CBM_RESOLVE_ANALYSIS_PENDING;
|
|
break;
|
|
case CBM_SOLVER_UNKNOWN:
|
|
value.analysis = CBM_RESOLVE_ANALYSIS_UNKNOWN;
|
|
break;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static bool same_value(cbm_resolve_solver_value_t left, cbm_resolve_solver_value_t right) {
|
|
return left.target && right.target && !strcmp(left.target, right.target) &&
|
|
left.kind == right.kind && left.module == right.module &&
|
|
left.fact_kind == right.fact_kind &&
|
|
!strcmp(left.type_identity ? left.type_identity : "",
|
|
right.type_identity ? right.type_identity : "") &&
|
|
!strcmp(left.declaration_identity ? left.declaration_identity : "",
|
|
right.declaration_identity ? right.declaration_identity : "");
|
|
}
|
|
|
|
static const cbm_resolve_proof_t *missing_proof(cbm_resolve_solver_t *solver,
|
|
const solver_node_t *node, const char *key) {
|
|
const cbm_resolve_proof_view_t *audit = cbm_resolve_proof_view(node->audit);
|
|
cbm_resolve_proof_key_t lookup = {.lookup_domain = "named_dependency_solver",
|
|
.lookup_key = key,
|
|
.detail = "missing_registered_fact",
|
|
.lookup_closed = true};
|
|
cbm_resolve_result_t missing = {.analysis = CBM_RESOLVE_ANALYSIS_COMPLETE,
|
|
.reason = "missing_registered_fact"};
|
|
return cbm_resolve_proof_leaf(solver->pool, CBM_PROOF_ADAPTER_DOMAIN, &audit->source, &lookup,
|
|
&missing);
|
|
}
|
|
|
|
static bool settle(cbm_resolve_solver_t *solver, solver_node_t *node, bool cycle) {
|
|
solver->steps++;
|
|
const cbm_resolve_proof_view_t *audit = cbm_resolve_proof_view(node->audit);
|
|
cbm_resolve_solver_result_t *result = &node->result;
|
|
result->predecessors_closed = !cycle;
|
|
if (node->operation == SOLVER_LEAF) {
|
|
result->status = node->leaf_status;
|
|
if (result->status == CBM_SOLVER_KNOWN)
|
|
result->status = analysis_status(audit->outcome.analysis);
|
|
if (result->status == CBM_SOLVER_KNOWN)
|
|
result->value = node->leaf_value;
|
|
result->proof = node->audit;
|
|
return true;
|
|
}
|
|
size_t count = result->dependency_count;
|
|
const cbm_resolve_proof_t **premises =
|
|
cbm_arena_alloc(solver->arena, (count + 1) * sizeof(*premises));
|
|
if (!premises)
|
|
return false;
|
|
size_t premise_count = 1;
|
|
premises[0] = node->audit;
|
|
result->status = cycle ? CBM_SOLVER_UNKNOWN : analysis_status(audit->outcome.analysis);
|
|
bool have_value = false;
|
|
for (size_t i = 0; i < count; i++) {
|
|
solver->steps++;
|
|
cbm_resolve_stats_add(CBM_RESOLVE_STAT_PROPAGATED_FACTS, 1);
|
|
size_t index = node->dependency_indices[i];
|
|
const cbm_resolve_solver_result_t *dependency =
|
|
index == SIZE_MAX ? NULL : &solver->nodes[index]->result;
|
|
if (cycle && dependency && dependency->status == CBM_SOLVER_PENDING)
|
|
continue; /* The full dependency KEY set remains in the open result. */
|
|
cbm_resolve_solver_status_t status = dependency ? dependency->status : CBM_SOLVER_UNKNOWN;
|
|
const cbm_resolve_proof_t *proof =
|
|
dependency ? dependency->proof : missing_proof(solver, node, result->dependencies[i]);
|
|
if (!proof)
|
|
return false;
|
|
premises[premise_count++] = proof;
|
|
if (status != CBM_SOLVER_KNOWN) {
|
|
if (status > result->status)
|
|
result->status = status;
|
|
} else if (node->operation == SOLVER_SELECT &&
|
|
!strcmp(result->dependencies[i], node->selector)) {
|
|
continue; /* Selector proves the domain, not the selected value. */
|
|
} else if (!have_value) {
|
|
result->value = dependency->value;
|
|
have_value = true;
|
|
} else if (!same_value(result->value, dependency->value) &&
|
|
result->status < CBM_SOLVER_AMBIGUOUS) {
|
|
result->status = CBM_SOLVER_AMBIGUOUS;
|
|
}
|
|
}
|
|
if (node->operation == SOLVER_TYPE && result->status == CBM_SOLVER_KNOWN && have_value) {
|
|
const CBMType *type = result->value.type;
|
|
if (!type || (node->wrapper != CBM_TYPE_NAMED && node->wrapper != CBM_TYPE_POINTER &&
|
|
node->wrapper != CBM_TYPE_SLICE)) {
|
|
result->status = CBM_SOLVER_UNSUPPORTED;
|
|
} else {
|
|
if (node->wrapper == CBM_TYPE_POINTER)
|
|
type = cbm_type_pointer(solver->arena, type);
|
|
else if (node->wrapper == CBM_TYPE_SLICE)
|
|
type = cbm_type_slice(solver->arena, type);
|
|
if (node->wrapper != CBM_TYPE_NAMED)
|
|
result->value.type_identity = cbm_arena_sprintf(
|
|
solver->arena, "%u:(%s)", (unsigned)node->wrapper, result->value.type_identity);
|
|
if (!type || !result->value.type_identity)
|
|
return false;
|
|
result->value.type = type;
|
|
result->value.fact_kind = CBM_SOLVER_FACT_TYPE;
|
|
result->value.binding_version = node->version;
|
|
}
|
|
}
|
|
if (!have_value && result->status == CBM_SOLVER_KNOWN)
|
|
result->status = CBM_SOLVER_UNKNOWN;
|
|
if (result->status != CBM_SOLVER_KNOWN || !have_value)
|
|
result->value = (cbm_resolve_solver_value_t){0};
|
|
cbm_resolve_proof_key_t key = {.lookup_domain = "named_dependency_solver",
|
|
.lookup_key = result->key,
|
|
.binding_identity = result->key,
|
|
.type_identity = result->value.type_identity,
|
|
.declaration_identity = result->value.declaration_identity,
|
|
.detail = cycle ? "unsettled_cycle" : "closed_dependency_set",
|
|
.flags = result->value.kind};
|
|
cbm_resolve_result_t resolved = outcome(result);
|
|
/* An open cycle may have no settled predecessor proof. Its source audit
|
|
* remains a leaf witness, while predecessors_closed=false prevents any
|
|
* consumer from treating the incomplete derivation as a binding proof. */
|
|
cbm_resolve_proof_kind_t kind = cycle ? CBM_PROOF_REGISTRY_LOOKUP
|
|
: node->operation == SOLVER_COPY ? CBM_PROOF_NAMED_COPY
|
|
: CBM_PROOF_NAMED_JOIN;
|
|
result->proof = cbm_resolve_proof_derive(solver->pool, kind, &audit->source, &key, &resolved,
|
|
premises, premise_count, !cycle);
|
|
return result->proof != NULL;
|
|
}
|
|
|
|
static void fail_results(cbm_resolve_solver_t *solver) {
|
|
for (size_t i = 0; i < solver->count; i++) {
|
|
cbm_resolve_solver_result_t *result = &solver->nodes[i]->result;
|
|
result->status = solver->failure;
|
|
result->value = (cbm_resolve_solver_value_t){0};
|
|
result->proof = NULL;
|
|
result->predecessors_closed = false;
|
|
}
|
|
}
|
|
|
|
bool cbm_resolve_solver_solve(cbm_resolve_solver_t *solver) {
|
|
if (!solver || solver->solved)
|
|
return false;
|
|
solver->solved = true;
|
|
if (solver->selections > solver->limits.edges - solver->edge_count)
|
|
solver_fail(solver, CBM_SOLVER_LIMITED);
|
|
size_t reserved_edges = solver->edge_count + solver->selections;
|
|
if (solver->count > SIZE_MAX / sizeof(size_t) ||
|
|
reserved_edges > SIZE_MAX / sizeof(solver_reverse_edge_t) ||
|
|
reserved_edges > (UINT64_MAX - solver->count) / 4 ||
|
|
solver->count + (uint64_t)solver->edge_count * 3 + (uint64_t)solver->selections * 4 >
|
|
solver->limits.steps)
|
|
solver_fail(solver, CBM_SOLVER_LIMITED);
|
|
if (solver->failure != CBM_SOLVER_PENDING) {
|
|
fail_results(solver);
|
|
return solver->failure != CBM_SOLVER_FAILED;
|
|
}
|
|
if (solver->count > 1)
|
|
qsort(solver->nodes, solver->count, sizeof(*solver->nodes), compare_nodes);
|
|
for (size_t i = 1; i < solver->count; i++)
|
|
if (!strcmp(solver->nodes[i - 1]->result.key, solver->nodes[i]->result.key))
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
if (solver->failure != CBM_SOLVER_PENDING) {
|
|
fail_results(solver);
|
|
return false;
|
|
}
|
|
size_t *heap =
|
|
solver->count ? cbm_arena_alloc(solver->arena, solver->count * sizeof(*heap)) : NULL;
|
|
solver_reverse_edge_t *reverse =
|
|
reserved_edges ? cbm_arena_alloc(solver->arena, reserved_edges * sizeof(*reverse)) : NULL;
|
|
if ((solver->count && !heap) || (reserved_edges && !reverse)) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
fail_results(solver);
|
|
return false;
|
|
}
|
|
size_t edge = 0, ready = 0;
|
|
for (size_t n = 0; n < solver->count; n++) {
|
|
solver_node_t *node = solver->nodes[n];
|
|
size_t count = node->result.dependency_count;
|
|
node->dependency_indices =
|
|
count ? cbm_arena_alloc(solver->arena, count * sizeof(*node->dependency_indices))
|
|
: NULL;
|
|
if (count && !node->dependency_indices) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
break;
|
|
}
|
|
for (size_t d = 0; d < count; d++) {
|
|
solver->steps++;
|
|
size_t target = find_node(solver, node->result.dependencies[d]);
|
|
node->dependency_indices[d] = target;
|
|
if (target != SIZE_MAX) {
|
|
reverse[edge] = (solver_reverse_edge_t){n, solver->nodes[target]->reverse_head};
|
|
solver->nodes[target]->reverse_head = edge++;
|
|
node->remaining++;
|
|
}
|
|
}
|
|
if (!node->remaining)
|
|
heap_push(heap, &ready, n);
|
|
}
|
|
while (ready && solver->failure == CBM_SOLVER_PENDING) {
|
|
size_t n = heap_pop(heap, &ready);
|
|
solver_node_t *node = solver->nodes[n];
|
|
if (node->operation == SOLVER_SELECT && !node->selected) {
|
|
node->selected = true;
|
|
size_t selector_index = node->dependency_indices[0];
|
|
const cbm_resolve_solver_result_t *selector =
|
|
selector_index == SIZE_MAX ? NULL : &solver->nodes[selector_index]->result;
|
|
const char *identity = selector ? (node->select_type ? selector->value.type_identity
|
|
: selector->value.target)
|
|
: NULL;
|
|
if (selector && selector->status == CBM_SOLVER_KNOWN && identity && *identity) {
|
|
const char *selected = cbm_arena_sprintf(solver->arena, "%s%s%s", node->prefix,
|
|
identity, node->suffix);
|
|
const char **keys = cbm_arena_alloc(solver->arena, 2 * sizeof(*keys));
|
|
size_t *indices = cbm_arena_alloc(solver->arena, 2 * sizeof(*indices));
|
|
if (!selected || !keys || !indices) {
|
|
solver_fail(solver, CBM_SOLVER_FAILED);
|
|
break;
|
|
}
|
|
keys[0] = node->selector;
|
|
keys[1] = selected;
|
|
if (strcmp(keys[0], keys[1]) > 0) {
|
|
const char *swap = keys[0];
|
|
keys[0] = keys[1];
|
|
keys[1] = swap;
|
|
}
|
|
indices[0] = find_node(solver, keys[0]);
|
|
indices[1] = find_node(solver, keys[1]);
|
|
node->result.dependencies = keys;
|
|
node->result.dependency_count = 2;
|
|
node->dependency_indices = indices;
|
|
size_t target = find_node(solver, selected);
|
|
solver->steps++;
|
|
if (target != SIZE_MAX &&
|
|
solver->nodes[target]->result.status == CBM_SOLVER_PENDING) {
|
|
reverse[edge] = (solver_reverse_edge_t){n, solver->nodes[target]->reverse_head};
|
|
solver->nodes[target]->reverse_head = edge++;
|
|
node->remaining = 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (!settle(solver, node, false)) {
|
|
solver_fail(solver, solver->pool->failure == CBM_RESOLVE_ANALYSIS_LIMITED
|
|
? CBM_SOLVER_LIMITED
|
|
: CBM_SOLVER_FAILED);
|
|
break;
|
|
}
|
|
for (size_t r = node->reverse_head; r != SIZE_MAX; r = reverse[r].next) {
|
|
solver->steps++;
|
|
size_t dependent = reverse[r].dependent;
|
|
if (!--solver->nodes[dependent]->remaining)
|
|
heap_push(heap, &ready, dependent);
|
|
}
|
|
}
|
|
for (size_t n = 0; n < solver->count && solver->failure == CBM_SOLVER_PENDING; n++) {
|
|
if (solver->nodes[n]->result.status == CBM_SOLVER_PENDING &&
|
|
!settle(solver, solver->nodes[n], true))
|
|
solver_fail(solver, solver->pool->failure == CBM_RESOLVE_ANALYSIS_LIMITED
|
|
? CBM_SOLVER_LIMITED
|
|
: CBM_SOLVER_FAILED);
|
|
}
|
|
if (solver->failure != CBM_SOLVER_PENDING)
|
|
fail_results(solver);
|
|
return solver->failure != CBM_SOLVER_FAILED;
|
|
}
|
|
|
|
void cbm_resolve_solver_get(const cbm_resolve_solver_t *solver, const char *key,
|
|
cbm_resolve_solver_result_t *out) {
|
|
if (!out)
|
|
return;
|
|
*out = (cbm_resolve_solver_result_t){.key = key, .status = CBM_SOLVER_PENDING};
|
|
if (!solver || !key) {
|
|
out->status = CBM_SOLVER_FAILED;
|
|
return;
|
|
}
|
|
if (!solver->solved)
|
|
return;
|
|
if (solver->failure != CBM_SOLVER_PENDING) {
|
|
out->status = solver->failure;
|
|
return;
|
|
}
|
|
size_t index = find_node(solver, key);
|
|
if (index != SIZE_MAX)
|
|
*out = solver->nodes[index]->result;
|
|
else
|
|
out->status = CBM_SOLVER_UNKNOWN;
|
|
}
|
|
|
|
size_t cbm_resolve_solver_count(const cbm_resolve_solver_t *solver) {
|
|
return cbm_resolve_solver_complete(solver) ? solver->count : 0;
|
|
}
|
|
|
|
const cbm_resolve_solver_result_t *cbm_resolve_solver_at(const cbm_resolve_solver_t *solver,
|
|
size_t index) {
|
|
return cbm_resolve_solver_complete(solver) && index < solver->count
|
|
? &solver->nodes[index]->result
|
|
: NULL;
|
|
}
|
|
|
|
uint64_t cbm_resolve_solver_steps(const cbm_resolve_solver_t *solver) {
|
|
return solver ? solver->steps : 0;
|
|
}
|
|
|
|
bool cbm_resolve_solver_complete(const cbm_resolve_solver_t *solver) {
|
|
return solver && solver->solved && solver->failure == CBM_SOLVER_PENDING;
|
|
}
|