feat: implement Phase 4d const-fold transform for select AST
Adds sel_const_fold_pass() — a single-pass bottom-up const-fold that
runs after the Phase 4c flatten loop in parse_select():
4d-1 EQ/NE with either operand SEL_ANY → SEL_TRUE / SEL_FALSE
(checks both child slots — commutativity sort makes SEL_ANY
position non-deterministic after makeSelectNode interning)
4d-2 Identity laws: TRUE&&X→X, X&&TRUE→X, FALSE||X→X, X||FALSE→X
4d-3 Annihilation laws: FALSE&&X→FALSE, TRUE||X→TRUE (symmetric)
4d-4 Complement laws: !TRUE→FALSE, !FALSE→TRUE
Memory semantics:
level==0 (API server): frees orphaned nodes via free_select_node /
sel_free_tree; OOM from makeSelectNode returns original node safely
level>0 (CSE/scheduler): never manually frees; annihilation folds
return the already-interned left/right singleton directly (no
redundant makeSelectNode call); OOM guard unconditional so CSE
mode also degrades gracefully instead of returning NULL
9 new TDD tests added to test_select.c (RED→GREEN verified).
All 166 tests pass (157 prior + 9 Phase 4d).
This commit is contained in:
parent
c2ad96a5e0
commit
b38babb7d6
9 changed files with 3248 additions and 5 deletions
159
src/lib/resreq.c
159
src/lib/resreq.c
|
|
@ -1,4 +1,7 @@
|
|||
#define _XOPEN_SOURCE 700
|
||||
|
||||
#include <errno.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -8,10 +11,24 @@
|
|||
#include "rusage.h"
|
||||
#include "section.h"
|
||||
#include "utils.h"
|
||||
#include "xce/types.h"
|
||||
#include "xce_resreq.h"
|
||||
#include "xce_select_peg.h"
|
||||
#include "xlist.h"
|
||||
#include "xvec.h"
|
||||
|
||||
res_type_t valtype_to_res_type(int v)
|
||||
{
|
||||
switch (v) {
|
||||
case XCE_VAL_NUMERIC: return RES_TYPE_NUMERIC;
|
||||
case XCE_VAL_STRING: return RES_TYPE_STRING;
|
||||
case XCE_VAL_BOOLEAN: return RES_TYPE_BOOL;
|
||||
default:
|
||||
ERROR("valtype_to_res_type: unknown valtype %d", v);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static int get_parse_errmsg(char** errmsg, const char* data, int pos)
|
||||
{
|
||||
size_t bufsize = 0;
|
||||
|
|
@ -153,8 +170,127 @@ int parse_rusage(xlist_t* rusages, char* data, char** errmsg)
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* Depth pre-scan: count '!' and '(' as depth increments.
|
||||
* Returns 0 if depth is within limit, -1 if exceeded. */
|
||||
static int check_select_depth(const char* input, int max_depth)
|
||||
{
|
||||
int depth = 0;
|
||||
const char* p;
|
||||
for (p = input; *p; p++) {
|
||||
if (*p == '!' || *p == '(') {
|
||||
depth++;
|
||||
if (depth > max_depth) return -1;
|
||||
} else if (*p == ')') {
|
||||
if (depth > 0) depth--;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_select(xce_primary_resreq_t* primary, char* selectString,
|
||||
var resmgr, char** errmsg)
|
||||
{
|
||||
select_context_t ctx;
|
||||
xce_select_context_t* p = NULL;
|
||||
int rc = 1;
|
||||
|
||||
/* Without a resource manager, resource names cannot be resolved.
|
||||
* Leave primary->select = NULL and succeed — callers that need
|
||||
* select evaluation must supply a valid resmgr. */
|
||||
if (resmgr == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
|
||||
/* Length guard */
|
||||
if (strlen(selectString) > SELECT_MAX_INPUT_LEN) {
|
||||
*errmsg = strdup("select expression exceeds maximum length");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Depth guard */
|
||||
if (check_select_depth(selectString, SELECT_MAX_DEPTH) < 0) {
|
||||
*errmsg = strdup("select expression exceeds maximum nesting depth");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctx.resmgr = resmgr;
|
||||
ctx.input = selectString;
|
||||
|
||||
p = xce_select_create(&ctx);
|
||||
if (p == NULL) return -errno;
|
||||
|
||||
/* LC_NUMERIC guard: strtod/snprintf in PEG actions must use '.' as the
|
||||
* decimal separator regardless of the calling thread's locale setting. */
|
||||
locale_t cloc = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
|
||||
if (cloc == (locale_t)0) {
|
||||
xce_select_destroy(p);
|
||||
if (sel_get_optimize_level() > 0) {
|
||||
ERROR("newlocale(LC_NUMERIC_MASK,C) failed: %s — scheduler cannot "
|
||||
"safely parse numeric resreq values", strerror(errno));
|
||||
abort();
|
||||
}
|
||||
ERROR("newlocale(LC_NUMERIC_MASK,C) failed: %s — rejecting resreq parse",
|
||||
strerror(errno));
|
||||
*errmsg = strdup("locale initialization failed");
|
||||
return -EINVAL;
|
||||
}
|
||||
locale_t old = uselocale(cloc);
|
||||
|
||||
xce_select_parse(p, NULL);
|
||||
|
||||
uselocale(old);
|
||||
freelocale(cloc);
|
||||
|
||||
if (ctx.has_error || ctx.root == NULL) {
|
||||
if (ctx.errmsg[0] != '\0')
|
||||
*errmsg = strdup(ctx.errmsg);
|
||||
else
|
||||
get_parse_errmsg(errmsg, selectString, ctx.err_pos);
|
||||
rc = -EINVAL;
|
||||
goto cleanup_parser;
|
||||
}
|
||||
|
||||
/* Phase 4c: flatten AND/OR right-associative chains to left-associative */
|
||||
{
|
||||
int flat_changed;
|
||||
do {
|
||||
flat_changed = 0;
|
||||
ctx.root = sel_flatten_pass(ctx.root, &flat_changed);
|
||||
if (ctx.root == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto cleanup_parser;
|
||||
}
|
||||
} while (flat_changed);
|
||||
}
|
||||
|
||||
/* Phase 4d: const-fold (single pass — does not create new flatten opportunities) */
|
||||
ctx.root = sel_const_fold_pass(ctx.root);
|
||||
if (ctx.root == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto cleanup_parser;
|
||||
}
|
||||
|
||||
{
|
||||
xce_select_t* sel = calloc(1, sizeof(*sel));
|
||||
if (sel == NULL) {
|
||||
if (sel_get_optimize_level() == 0)
|
||||
sel_free_tree(ctx.root);
|
||||
rc = -ENOMEM;
|
||||
} else {
|
||||
sel->root = ctx.root;
|
||||
primary->select = sel;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_parser:
|
||||
xce_select_destroy(p);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int parse_resreq_primary(xce_primary_resreq_t* primary, char* resreqString,
|
||||
char** errmsg)
|
||||
var resmgr, char** errmsg)
|
||||
{
|
||||
int rc;
|
||||
xce_resreq_data_t data;
|
||||
|
|
@ -170,6 +306,15 @@ int parse_resreq_primary(xce_primary_resreq_t* primary, char* resreqString,
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* Parse select clause BEFORE kill_resreq_data (data.selectString freed there) */
|
||||
if (data.selectString != NULL && data.selectString[0] != '\0') {
|
||||
rc = parse_select(primary, data.selectString, resmgr, errmsg);
|
||||
if (rc < 0) {
|
||||
kill_resreq_data(&data);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
xlist_init(&(primary->rusages), 8);
|
||||
|
||||
if ((data.rusageString != NULL) && (strlen(data.rusageString) != 0)) {
|
||||
|
|
@ -197,7 +342,7 @@ void kill_resreq_ctx(resreq_context_t* ctx)
|
|||
}
|
||||
}
|
||||
|
||||
int parse_resreq(xce_resreq_t* resreq, char* data, char** errmsg)
|
||||
int parse_resreq(xce_resreq_t* resreq, char* data, var resmgr, char** errmsg)
|
||||
{
|
||||
resreq_context_t ctx = { 0 };
|
||||
xce_resreq_context_t* p = NULL;
|
||||
|
|
@ -210,6 +355,7 @@ int parse_resreq(xce_resreq_t* resreq, char* data, char** errmsg)
|
|||
*errmsg = NULL;
|
||||
memset(resreq, 0x0, sizeof(*resreq));
|
||||
|
||||
ctx.resmgr = resmgr;
|
||||
ctx.input = data;
|
||||
ctx.resreq = resreq;
|
||||
|
||||
|
|
@ -241,6 +387,15 @@ void kill_primary_resreq(xce_primary_resreq_t* resreq)
|
|||
return;
|
||||
}
|
||||
|
||||
if (resreq->select != NULL) {
|
||||
xce_select_node_t* root = resreq->select->root;
|
||||
resreq->select->root = NULL;
|
||||
if (sel_get_optimize_level() == 0 && root != NULL)
|
||||
sel_free_tree(root);
|
||||
FREEUP(resreq->select);
|
||||
}
|
||||
resreq->select = NULL;
|
||||
|
||||
for (i = 0; i < resreq->rusages.count; i++) {
|
||||
rusage_value_t* v = (rusage_value_t*)xlist_get(&(resreq->rusages), i);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue