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
260
src/lib/xce_select.peg
Normal file
260
src/lib/xce_select.peg
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
%prefix "xce_select"
|
||||
%value "xce_select_node_t *"
|
||||
%auxil "select_context_t *"
|
||||
%source {
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "xstring.h"
|
||||
|
||||
#define PCC_ERROR(auxil) xce_select_parse_error(auxil)
|
||||
|
||||
static void xce_select_parse_error(select_context_t *auxil) {
|
||||
auxil->has_error = 1;
|
||||
}
|
||||
|
||||
/* Resolve a resource name: clause-keyword check then resmgr lookup.
|
||||
* Returns a SEL_RESNAME node on success, NULL with has_error set on failure. */
|
||||
static xce_select_node_t* resolve_resname(select_context_t* auxil,
|
||||
const char* name)
|
||||
{
|
||||
static const char* clause_keywords[] = {
|
||||
"select", "rusage", "span", "same", "affinity", NULL
|
||||
};
|
||||
int i;
|
||||
for (i = 0; clause_keywords[i]; i++) {
|
||||
if (strcmp(name, clause_keywords[i]) == 0) {
|
||||
snprintf(auxil->errmsg, sizeof(auxil->errmsg),
|
||||
"resource name '%s' conflicts with a clause keyword — "
|
||||
"use explicit select[%s ...] form if this is intentional",
|
||||
name, name);
|
||||
auxil->has_error = 1;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
xce_resource_t* r = NULL;
|
||||
int rc = key_find(auxil->resmgr, (void*)name, (void**)&r);
|
||||
if (rc <= 0 || r == NULL) {
|
||||
if (strcmp(name, "true") == 0 || strcmp(name, "false") == 0) {
|
||||
snprintf(auxil->errmsg, sizeof(auxil->errmsg),
|
||||
"unknown resource '%s' — did you mean the boolean resource "
|
||||
"name directly (e.g. 'gpu') or a numeric literal (1/0)?",
|
||||
name);
|
||||
} else {
|
||||
snprintf(auxil->errmsg, sizeof(auxil->errmsg),
|
||||
"unknown resource '%s'", name);
|
||||
}
|
||||
auxil->has_error = 1;
|
||||
return NULL;
|
||||
}
|
||||
res_type_t res_type = valtype_to_res_type(r->valtype);
|
||||
return (xce_select_node_t*)makeSelectNode(SEL_RESNAME,
|
||||
(uintptr_t)r->idx,
|
||||
(uintptr_t)res_type);
|
||||
}
|
||||
}
|
||||
%common {
|
||||
#include "kv.h"
|
||||
#include "resreq.h"
|
||||
|
||||
#define PCC_GETCHAR(auxil) \
|
||||
({ int c = (auxil->input[auxil->pos] != '\0') ? (int)auxil->input[auxil->pos ++]: -1; \
|
||||
if (auxil->pos > auxil->err_pos) auxil->err_pos = auxil->pos; \
|
||||
c; \
|
||||
})
|
||||
}
|
||||
|
||||
statement <- _ e:select_or _ EOF {
|
||||
auxil->root = e;
|
||||
$$ = e;
|
||||
}
|
||||
|
||||
select_or <- l:select_and _ "||" _ r:select_or {
|
||||
if (auxil->has_error || l == NULL || r == NULL) {
|
||||
$$ = NULL;
|
||||
} else {
|
||||
$$ = (xce_select_node_t*)makeSelectNode(SEL_OR, (uintptr_t)l, (uintptr_t)r);
|
||||
if ($$ == NULL) {
|
||||
auxil->has_error = 1;
|
||||
if (sel_get_optimize_level() == 0) {
|
||||
sel_free_tree(l);
|
||||
sel_free_tree(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/ e:select_and { $$ = e; }
|
||||
|
||||
select_and <- l:select_not _ "&&" _ r:select_and {
|
||||
if (auxil->has_error || l == NULL || r == NULL) {
|
||||
$$ = NULL;
|
||||
} else {
|
||||
$$ = (xce_select_node_t*)makeSelectNode(SEL_AND, (uintptr_t)l, (uintptr_t)r);
|
||||
if ($$ == NULL) {
|
||||
auxil->has_error = 1;
|
||||
if (sel_get_optimize_level() == 0) {
|
||||
sel_free_tree(l);
|
||||
sel_free_tree(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/ e:select_not { $$ = e; }
|
||||
|
||||
select_not <- "!" _ e:select_primary {
|
||||
if (auxil->has_error || e == NULL) {
|
||||
$$ = NULL;
|
||||
} else {
|
||||
$$ = (xce_select_node_t*)makeSelectNode(SEL_NOT, (uintptr_t)e, 0);
|
||||
if ($$ == NULL) {
|
||||
auxil->has_error = 1;
|
||||
if (sel_get_optimize_level() == 0)
|
||||
sel_free_tree(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
/ e:select_primary { $$ = e; }
|
||||
|
||||
select_primary <- "(" _ e:select_or _ ")" { $$ = e; }
|
||||
/ e:comparison { $$ = e; }
|
||||
/ e:resname_leaf { $$ = e; }
|
||||
|
||||
comparison <- l:resname_node _ op:cmp_op _ r:value_node {
|
||||
/* SEL_DISCARD: free a speculatively allocated node that the grammar
|
||||
* action decides not to use. Only frees in level-0 (non-CSE) mode;
|
||||
* in CSE mode the intern table owns the node and GC reclaims it. */
|
||||
#define SEL_DISCARD(n) do { \
|
||||
if ((n) != NULL && sel_get_optimize_level() == 0) { \
|
||||
if ((n)->type == SEL_STR && (n)->u.strval != NULL) \
|
||||
dereference((n)->u.strval); \
|
||||
free(n); \
|
||||
(n) = NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
if (auxil->has_error || l == NULL || r == NULL) {
|
||||
SEL_DISCARD(l);
|
||||
SEL_DISCARD(r);
|
||||
$$ = NULL;
|
||||
} else {
|
||||
sel_node_type_t op_type = (sel_node_type_t)(uintptr_t)op;
|
||||
if (op_type == SEL_EQ || op_type == SEL_NE) {
|
||||
/* 4b-2: EQ/NE type validation */
|
||||
if (l->u.resname.res_type == RES_TYPE_BOOL &&
|
||||
r->type == SEL_STR) {
|
||||
/* BOOL resource vs string literal: reject */
|
||||
snprintf(auxil->errmsg, sizeof(auxil->errmsg),
|
||||
"type mismatch: boolean resource cannot be compared "
|
||||
"with a string literal");
|
||||
auxil->has_error = 1;
|
||||
SEL_DISCARD(l);
|
||||
SEL_DISCARD(r);
|
||||
$$ = NULL;
|
||||
} else if (l->u.resname.res_type == RES_TYPE_STRING &&
|
||||
r->type == SEL_NUM) {
|
||||
/* Coerce SEL_NUM → SEL_STR when LHS is STRING type */
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%.17g", r->u.numval);
|
||||
char* s = makeString(buf);
|
||||
if (s == NULL) {
|
||||
auxil->has_error = 1;
|
||||
SEL_DISCARD(l);
|
||||
SEL_DISCARD(r);
|
||||
$$ = NULL;
|
||||
} else {
|
||||
if (sel_get_optimize_level() == 0) free(r);
|
||||
r = (xce_select_node_t*)makeSelectNode(SEL_STR,
|
||||
(uintptr_t)s,
|
||||
0);
|
||||
if (r == NULL) {
|
||||
auxil->has_error = 1;
|
||||
SEL_DISCARD(l);
|
||||
$$ = NULL;
|
||||
} else {
|
||||
$$ = (xce_select_node_t*)makeSelectNode(op_type,
|
||||
(uintptr_t)l,
|
||||
(uintptr_t)r);
|
||||
if ($$ == NULL) {
|
||||
auxil->has_error = 1;
|
||||
SEL_DISCARD(l);
|
||||
SEL_DISCARD(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$$ = (xce_select_node_t*)makeSelectNode(op_type,
|
||||
(uintptr_t)l,
|
||||
(uintptr_t)r);
|
||||
if ($$ == NULL) {
|
||||
auxil->has_error = 1;
|
||||
SEL_DISCARD(l);
|
||||
SEL_DISCARD(r);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* 4b-3: Ordered comparison (GT/LT/GE/LE) type validation */
|
||||
if (l->u.resname.res_type != RES_TYPE_NUMERIC) {
|
||||
snprintf(auxil->errmsg, sizeof(auxil->errmsg),
|
||||
"type mismatch: ordered comparison requires a numeric "
|
||||
"resource on the left-hand side");
|
||||
auxil->has_error = 1;
|
||||
SEL_DISCARD(l);
|
||||
SEL_DISCARD(r);
|
||||
$$ = NULL;
|
||||
} else if (r->type != SEL_NUM) {
|
||||
snprintf(auxil->errmsg, sizeof(auxil->errmsg),
|
||||
"type mismatch: ordered comparison requires a numeric "
|
||||
"literal on the right-hand side");
|
||||
auxil->has_error = 1;
|
||||
SEL_DISCARD(l);
|
||||
SEL_DISCARD(r);
|
||||
$$ = NULL;
|
||||
} else {
|
||||
$$ = (xce_select_node_t*)makeSelectNode(op_type,
|
||||
(uintptr_t)l,
|
||||
(uintptr_t)r);
|
||||
if ($$ == NULL) {
|
||||
auxil->has_error = 1;
|
||||
SEL_DISCARD(l);
|
||||
SEL_DISCARD(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#undef SEL_DISCARD
|
||||
}
|
||||
|
||||
resname_node <- !("any" ![a-zA-Z_0-9]) < [a-zA-Z_][a-zA-Z_0-9]* > {
|
||||
$$ = auxil->has_error ? NULL : resolve_resname(auxil, $0);
|
||||
}
|
||||
|
||||
resname_leaf <- !("any" ![a-zA-Z_0-9]) < [a-zA-Z_][a-zA-Z_0-9]* > {
|
||||
$$ = auxil->has_error ? NULL : resolve_resname(auxil, $0);
|
||||
}
|
||||
|
||||
value_node <- "any" ![a-zA-Z_0-9] {
|
||||
$$ = (xce_select_node_t*)makeSelectNode(SEL_ANY, 0, 0);
|
||||
}
|
||||
/ < [0-9]+ ("." [0-9]*)? > {
|
||||
double v = strtod($0, NULL);
|
||||
uintptr_t bits;
|
||||
memcpy(&bits, &v, sizeof(bits));
|
||||
$$ = (xce_select_node_t*)makeSelectNode(SEL_NUM, bits, 0);
|
||||
}
|
||||
/ < [a-zA-Z_][a-zA-Z_0-9]* > {
|
||||
char* s = makeString((char*)$0);
|
||||
if (s == NULL) { auxil->has_error = 1; $$ = NULL; }
|
||||
else { $$ = (xce_select_node_t*)makeSelectNode(SEL_STR, (uintptr_t)s, 0); }
|
||||
}
|
||||
|
||||
cmp_op <- ">=" { $$ = (xce_select_node_t*)(uintptr_t)SEL_GE; }
|
||||
/ "<=" { $$ = (xce_select_node_t*)(uintptr_t)SEL_LE; }
|
||||
/ "!=" { $$ = (xce_select_node_t*)(uintptr_t)SEL_NE; }
|
||||
/ "==" { $$ = (xce_select_node_t*)(uintptr_t)SEL_EQ; }
|
||||
/ ">" { $$ = (xce_select_node_t*)(uintptr_t)SEL_GT; }
|
||||
/ "<" { $$ = (xce_select_node_t*)(uintptr_t)SEL_LT; }
|
||||
|
||||
_ <- [ \t]*
|
||||
|
||||
EOF <- !.
|
||||
Loading…
Add table
Add a link
Reference in a new issue