docs: address document-review findings for resreq-ast-cse-design
Multi-persona review (coherence, feasibility, adversarial, product-lens) identified 25 findings. Applied 14 fixes: - Fix makeSelectNode key_add return code bug (rc2 → rc3) - Resolve derefSelectNode contradiction: remove function and refcount field, adopt GC-only reclamation model - Add boolean_literal grammar rule (true/false keywords) and SEL_BOOL_LITERAL node type + bval union member - Clarify transform pipeline: makeSelectNode does commutativity swap internally; flattening/const-fold are post-parse tree walks - Complete const-fold rules with annihilation laws - Add SEL_NUM bit-cast semantics (±0 distinction, NaN payloads) - Add resmgr=NULL fallback behavior spec - Add runtime type safety assertions for debug builds - Add GC trigger and session-based scheduler context - Fix section numbering gap (renumber §5.6 → §5.5) - Add gc_sweep_select_nodes/gc_mark_tree to Phase 2 declarations - Update kill_primary_resreq instruction for GC model - Add any keyword semantic inconsistency note (!= vs !(==)) 4 open issues remain for implementation phase: string resource interface, single-goroutine enforcement, kvtab iteration API, CSE benefit quantification.
This commit is contained in:
parent
33b76d9a77
commit
877741d008
1 changed files with 94 additions and 54 deletions
|
|
@ -101,10 +101,11 @@ atom_expr ::= '(' or_expr ')' | comparison | resname
|
||||||
comparison ::= resname cmp_op value
|
comparison ::= resname cmp_op value
|
||||||
cmp_op ::= '>=' | '<=' | '!=' | '==' | '>' | '<'
|
cmp_op ::= '>=' | '<=' | '!=' | '==' | '>' | '<'
|
||||||
resname ::= [a-zA-Z_][a-zA-Z_0-9]*
|
resname ::= [a-zA-Z_][a-zA-Z_0-9]*
|
||||||
value ::= string_literal | number | ANY
|
value ::= string_literal | number | boolean_literal | ANY
|
||||||
|
boolean_literal ::= 'true' | 'false'
|
||||||
ANY ::= 'any'
|
ANY ::= 'any'
|
||||||
number ::= [0-9]+ ('.' [0-9]*)?
|
number ::= [0-9]+ ('.' [0-9]*)?
|
||||||
string_literal ::= [a-zA-Z_][a-zA-Z_0-9]* (unquoted word, excluding 'any')
|
string_literal ::= [a-zA-Z_][a-zA-Z_0-9]* (unquoted word, excluding 'any', 'true', 'false')
|
||||||
```
|
```
|
||||||
|
|
||||||
**LSF-compatible disambiguation:** `resname` and `string_literal` share
|
**LSF-compatible disambiguation:** `resname` and `string_literal` share
|
||||||
|
|
@ -121,6 +122,23 @@ only valid as the right-hand side of `==` or `!=` (e.g., `type == any`,
|
||||||
The const-fold pass (§6.3) reduces `resname == any` to `SEL_TRUE`, so
|
The const-fold pass (§6.3) reduces `resname == any` to `SEL_TRUE`, so
|
||||||
`eval_select` never encounters `SEL_ANY` at runtime.
|
`eval_select` never encounters `SEL_ANY` at runtime.
|
||||||
|
|
||||||
|
**Note on `!= any`:** `resname != any` is a parse error. However,
|
||||||
|
`!(resname == any)` const-folds to `!SEL_TRUE` which reduces to
|
||||||
|
`SEL_FALSE` — these are semantically equivalent. This inconsistency
|
||||||
|
exists because the parse-time rejection of `!= any` provides a clearer
|
||||||
|
error message for users, while the const-fold path handles the negated
|
||||||
|
form implicitly. If this inconsistency proves confusing in practice,
|
||||||
|
a future change can allow `resname != any` at parse time and const-fold
|
||||||
|
it to `SEL_FALSE`.
|
||||||
|
|
||||||
|
**Boolean literals:** `true` and `false` are parsed as `SEL_BOOL_LITERAL`
|
||||||
|
nodes. The value is stored as `int bval` (1 for true, 0 for false) in
|
||||||
|
the node's union. Type validation ensures that boolean literals only
|
||||||
|
appear in `==`/`!=` comparisons with `RES_TYPE_BOOL` resources, or as
|
||||||
|
standalone boolean operands (where they const-fold to `SEL_TRUE` or
|
||||||
|
`SEL_FALSE`). `true` and `false` are reserved keywords excluded from
|
||||||
|
the `string_literal` pattern.
|
||||||
|
|
||||||
### 3.2 Node Types
|
### 3.2 Node Types
|
||||||
|
|
||||||
```c
|
```c
|
||||||
|
|
@ -137,6 +155,7 @@ typedef enum {
|
||||||
SEL_RESNAME, /* leaf: resource variable */
|
SEL_RESNAME, /* leaf: resource variable */
|
||||||
SEL_NUM, /* leaf: numeric literal */
|
SEL_NUM, /* leaf: numeric literal */
|
||||||
SEL_STR, /* leaf: string literal */
|
SEL_STR, /* leaf: string literal */
|
||||||
|
SEL_BOOL_LITERAL, /* leaf: boolean literal (true/false) */
|
||||||
SEL_ANY, /* leaf: any keyword (const-folded to SEL_TRUE) */
|
SEL_ANY, /* leaf: any keyword (const-folded to SEL_TRUE) */
|
||||||
SEL_TRUE, /* leaf: constant true (from const fold) */
|
SEL_TRUE, /* leaf: constant true (from const fold) */
|
||||||
SEL_FALSE, /* leaf: constant false (from const fold) */
|
SEL_FALSE, /* leaf: constant false (from const fold) */
|
||||||
|
|
@ -148,7 +167,6 @@ typedef enum {
|
||||||
```c
|
```c
|
||||||
struct xce_select_node_ {
|
struct xce_select_node_ {
|
||||||
sel_node_type_t type;
|
sel_node_type_t type;
|
||||||
atomic_ulong refcount; /* intern table reference count */
|
|
||||||
uint32_t hashcode; /* cached at construction */
|
uint32_t hashcode; /* cached at construction */
|
||||||
unsigned gc_mark; /* set during mark-and-sweep GC (§5.6) */
|
unsigned gc_mark; /* set during mark-and-sweep GC (§5.6) */
|
||||||
union {
|
union {
|
||||||
|
|
@ -162,6 +180,7 @@ struct xce_select_node_ {
|
||||||
} resname;
|
} resname;
|
||||||
double numval; /* SEL_NUM */
|
double numval; /* SEL_NUM */
|
||||||
char* strval; /* SEL_STR — interned via makeString() */
|
char* strval; /* SEL_STR — interned via makeString() */
|
||||||
|
int bval; /* SEL_BOOL_LITERAL — 1 for true, 0 for false */
|
||||||
} u;
|
} u;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
@ -279,10 +298,17 @@ Key encoding per node type:
|
||||||
| `SEL_RESNAME` | `(uintptr_t)res_idx` | `0` |
|
| `SEL_RESNAME` | `(uintptr_t)res_idx` | `0` |
|
||||||
| `SEL_NUM` | bit-cast of `double` | `0` |
|
| `SEL_NUM` | bit-cast of `double` | `0` |
|
||||||
| `SEL_STR` | `(uintptr_t)xstring_ptr` | `0` |
|
| `SEL_STR` | `(uintptr_t)xstring_ptr` | `0` |
|
||||||
|
| `SEL_BOOL_LITERAL` | `(uintptr_t)bval` | `0` |
|
||||||
| `SEL_ANY` | `0` | `0` |
|
| `SEL_ANY` | `0` | `0` |
|
||||||
| `SEL_TRUE` | `0` | `0` |
|
| `SEL_TRUE` | `0` | `0` |
|
||||||
| `SEL_FALSE` | `0` | `0` |
|
| `SEL_FALSE` | `0` | `0` |
|
||||||
|
|
||||||
|
`SEL_NUM` uses the exact bit-pattern of the `double` value reinterpreted as
|
||||||
|
`uintptr_t` (via `memcpy` or union cast). Two numeric literals are
|
||||||
|
equivalent for CSE if their bit-patterns are identical; `0.0` and `-0.0`
|
||||||
|
are distinct CSE keys (they differ in the sign bit). NaN payloads are
|
||||||
|
also compared by bit-pattern.
|
||||||
|
|
||||||
`SEL_STR` uses the interned `xstring` pointer — since `makeString()` already
|
`SEL_STR` uses the interned `xstring` pointer — since `makeString()` already
|
||||||
canonicalizes strings (libxce's string de-dup mechanism maps identical
|
canonicalizes strings (libxce's string de-dup mechanism maps identical
|
||||||
strings to the same pointer), pointer equality IS string equality.
|
strings to the same pointer), pointer equality IS string equality.
|
||||||
|
|
@ -322,9 +348,8 @@ xce_select_node_t* makeSelectNode(sel_node_type_t type,
|
||||||
xce_select_node_t* node = NULL;
|
xce_select_node_t* node = NULL;
|
||||||
mylock_lock(&sel_node_table.lock);
|
mylock_lock(&sel_node_table.lock);
|
||||||
int rc = key_find(sel_node_table.table, &key, (void**)&node);
|
int rc = key_find(sel_node_table.table, &key, (void**)&node);
|
||||||
if (rc > 0) reference(node);
|
if (rc > 0) { mylock_unlock(&sel_node_table.lock); return node; }
|
||||||
mylock_unlock(&sel_node_table.lock);
|
mylock_unlock(&sel_node_table.lock);
|
||||||
if (rc > 0) return node; /* cache hit */
|
|
||||||
if (rc < 0) return NULL; /* key_find error (not ENOENT) */
|
if (rc < 0) return NULL; /* key_find error (not ENOENT) */
|
||||||
|
|
||||||
/* 4. alloc and fill */
|
/* 4. alloc and fill */
|
||||||
|
|
@ -332,7 +357,6 @@ xce_select_node_t* makeSelectNode(sel_node_type_t type,
|
||||||
if (node == NULL) { errno = ENOMEM; return NULL; }
|
if (node == NULL) { errno = ENOMEM; return NULL; }
|
||||||
node->type = type;
|
node->type = type;
|
||||||
node->hashcode = sel_key_hash(&key);
|
node->hashcode = sel_key_hash(&key);
|
||||||
atomic_store(&node->refcount, 1);
|
|
||||||
/* fill u.* from op1/op2 by type … */
|
/* fill u.* from op1/op2 by type … */
|
||||||
|
|
||||||
/* 5. insert — re-check under lock for TOCTOU safety */
|
/* 5. insert — re-check under lock for TOCTOU safety */
|
||||||
|
|
@ -341,14 +365,13 @@ xce_select_node_t* makeSelectNode(sel_node_type_t type,
|
||||||
int rc2 = key_find(sel_node_table.table, &key, (void**)&existing);
|
int rc2 = key_find(sel_node_table.table, &key, (void**)&existing);
|
||||||
if (rc2 > 0) {
|
if (rc2 > 0) {
|
||||||
/* another thread inserted first — use theirs, free ours */
|
/* another thread inserted first — use theirs, free ours */
|
||||||
reference(existing);
|
|
||||||
mylock_unlock(&sel_node_table.lock);
|
mylock_unlock(&sel_node_table.lock);
|
||||||
free(node);
|
free(node);
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
key_add(sel_node_table.table, &key, node);
|
int rc3 = key_add(sel_node_table.table, &key, node);
|
||||||
if (rc2 < 0) {
|
if (rc3 < 0) {
|
||||||
/* key_add failed (EEXIST check already passed, likely ENOMEM) */
|
/* key_add failed (likely ENOMEM or EEXIST from a race) */
|
||||||
mylock_unlock(&sel_node_table.lock);
|
mylock_unlock(&sel_node_table.lock);
|
||||||
free(node);
|
free(node);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -365,28 +388,18 @@ select intern table adds the re-check to prevent node leaks under
|
||||||
concurrent construction. An alternative is to hold the lock through
|
concurrent construction. An alternative is to hold the lock through
|
||||||
the entire sequence (simpler but longer critical section).
|
the entire sequence (simpler but longer critical section).
|
||||||
|
|
||||||
```c
|
`kill_primary_resreq()` in `resreq.c` must unlink the select root
|
||||||
void derefSelectNode(xce_select_node_t* node)
|
pointer (`primary->select->root = NULL`) before freeing the select struct.
|
||||||
{
|
Node reclamation is handled by mark-and-sweep GC (§5.5) — no explicit
|
||||||
if (node == NULL) return;
|
deref function is needed.
|
||||||
atomic_fetch_sub(&node->refcount, 1);
|
|
||||||
/* Reclamation is handled by mark-and-sweep GC (§5.6).
|
|
||||||
When a job completes, kill_primary_resreq calls derefSelectNode
|
|
||||||
on the root to decrement refcounts. The GC sweep between
|
|
||||||
sessions frees nodes that are no longer reachable from any
|
|
||||||
live job's resreq root. */
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`kill_primary_resreq()` in `resreq.c` must call
|
### 5.5 Mark-and-Sweep GC Between Scheduling Sessions
|
||||||
`derefSelectNode(primary->select->root)` before freeing the select struct.
|
|
||||||
|
|
||||||
### 5.6 Mark-and-Sweep GC Between Scheduling Sessions
|
Node reclamation is handled by a **mark-and-sweep** GC that runs
|
||||||
|
between scheduling sessions, when no evaluation is in progress. There
|
||||||
`derefSelectNode` only decrements refcounts — it does **not** free nodes
|
is no `derefSelectNode` function — when a job completes, its resreq
|
||||||
or remove them from the intern table. Reclamation is handled by a
|
entry is simply unlinked from the job (see §5.4); the next GC sweep
|
||||||
**mark-and-sweep** GC that runs between scheduling sessions, when no
|
reclaims unreachable nodes.
|
||||||
evaluation is in progress:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
gc_sweep_select_nodes()
|
gc_sweep_select_nodes()
|
||||||
|
|
@ -409,14 +422,22 @@ This approach is simple and correct:
|
||||||
- A node is reachable if and only if it is visited from at least one live
|
- A node is reachable if and only if it is visited from at least one live
|
||||||
job's resreq root. No reference provenance tracking is needed — the
|
job's resreq root. No reference provenance tracking is needed — the
|
||||||
mark flag is a single boolean per node.
|
mark flag is a single boolean per node.
|
||||||
- `derefSelectNode` is removed — when a job completes, its resreq entry
|
|
||||||
is simply unlinked from the job; the next GC sweep reclaims unreachable
|
|
||||||
nodes.
|
|
||||||
- The sweep runs only between sessions (no concurrent eval), so no
|
- The sweep runs only between sessions (no concurrent eval), so no
|
||||||
read-after-free risk exists.
|
read-after-free risk exists.
|
||||||
- `SEL_STR` nodes' `xstring` references are handled by the same mark
|
- `SEL_STR` nodes' `xstring` references are handled by the same mark
|
||||||
pass — if the SEL_STR node is reachable, the string stays interned.
|
pass — if the SEL_STR node is reachable, the string stays interned.
|
||||||
|
|
||||||
|
The scheduler is responsible for calling `gc_sweep_select_nodes()`
|
||||||
|
between scheduling sessions, typically after all dispatch/defer decisions
|
||||||
|
for the current batch of jobs have been made. The function is not
|
||||||
|
thread-safe with respect to concurrent `eval_select` calls, so the
|
||||||
|
scheduler must ensure no evaluations are in progress when GC runs.
|
||||||
|
|
||||||
|
Like IBM LSF, the XCE scheduler operates in sessions — each session
|
||||||
|
evaluates all pending jobs against candidate hosts, dispatches what it
|
||||||
|
can, then ends. Pre/post-session work (such as GC) is a natural fit
|
||||||
|
for this architecture.
|
||||||
|
|
||||||
**Pointer-based canonicalization note:** Sorting by `uintptr_t` produces a
|
**Pointer-based canonicalization note:** Sorting by `uintptr_t` produces a
|
||||||
deterministic total order within a single process lifetime. Across
|
deterministic total order within a single process lifetime. Across
|
||||||
process restarts, pointer addresses change (ASLR, heap layout), so the
|
process restarts, pointer addresses change (ASLR, heap layout), so the
|
||||||
|
|
@ -472,9 +493,9 @@ are statically determined at parse time:
|
||||||
resource value). The `any` keyword (§3.1) is only valid on the
|
resource value). The `any` keyword (§3.1) is only valid on the
|
||||||
right-hand side of `==` or `!=`. For `!=`, `resname != any` is a
|
right-hand side of `==` or `!=`. For `!=`, `resname != any` is a
|
||||||
parse error (nothing would not match any).
|
parse error (nothing would not match any).
|
||||||
- `SEL_TRUE && X` → `X`, `X && SEL_TRUE` → `X` (identity elimination)
|
- Identity laws:`SEL_TRUE && X` → `X`, `X && SEL_TRUE` → `X`; `SEL_FALSE || X` → `X`, `X || SEL_FALSE` → `X`
|
||||||
- `SEL_FALSE || X` → `X`, `X || SEL_FALSE` → `X` (identity elimination)
|
- Annihilation laws: `SEL_FALSE && X` → `SEL_FALSE`, `X && SEL_FALSE` → `SEL_FALSE`; `SEL_TRUE || X` → `SEL_TRUE`, `X || SEL_TRUE` → `SEL_TRUE`
|
||||||
- `!SEL_TRUE` → `SEL_FALSE`, `!SEL_FALSE` → `SEL_TRUE`
|
- Complement laws: `!SEL_TRUE` → `SEL_FALSE`, `!SEL_FALSE` → `SEL_TRUE`
|
||||||
|
|
||||||
The const-fold pass runs **after** construction and flattening, replacing
|
The const-fold pass runs **after** construction and flattening, replacing
|
||||||
subtrees with constant leaf nodes. Since all nodes are interned,
|
subtrees with constant leaf nodes. Since all nodes are interned,
|
||||||
|
|
@ -491,17 +512,23 @@ AST construction follows a fixed pipeline after the PEG parser produces
|
||||||
the raw tree:
|
the raw tree:
|
||||||
|
|
||||||
```
|
```
|
||||||
1. Parse (PEG → raw binary AST)
|
1. Parse (PEG → raw binary AST, each PEG action calls `makeSelectNode`
|
||||||
2. Commutativity canonicalization (operand swap, §6)
|
which performs commutativity canonicalization as part of CSE key
|
||||||
3. Associativity flattening (§6.2)
|
construction)
|
||||||
4. Const-fold (§6.3)
|
2. Post-parse tree walk: associativity flattening (§6.2)
|
||||||
5. CSE intern (makeSelectNode for each node bottom-up)
|
3. Post-parse tree walk: const-fold (§6.3)
|
||||||
|
4. Bottom-up re-intern via `makeSelectNode` for any modified subtrees
|
||||||
```
|
```
|
||||||
|
|
||||||
Steps 2–4 operate on the tree before interning (they construct new
|
Note: commutativity canonicalization (operand swap) happens *inside*
|
||||||
nodes via `makeSelectNode` which itself does the commutativity swap).
|
`makeSelectNode` as part of the CSE key lookup — it is not a separate
|
||||||
Step 5 re-interns any modified subtrees, ensuring CSE maximizes sharing
|
pipeline step. The PEG action for each binary operator calls
|
||||||
on the fully normalized tree.
|
`makeSelectNode`, which sorts `op1`/`op2` for commutative operators
|
||||||
|
before building the key. Flattening and const-fold are post-parse tree
|
||||||
|
walks that traverse the raw AST and call `makeSelectNode` to construct
|
||||||
|
new normalized subtrees. Since `makeSelectNode` already handles interning
|
||||||
|
and canonicalization, these walks naturally produce the maximally shared
|
||||||
|
tree.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -578,6 +605,14 @@ See §12 for the formal declaration.
|
||||||
|
|
||||||
Unknown resource names fail the parse (rejected at job submission time).
|
Unknown resource names fail the parse (rejected at job submission time).
|
||||||
|
|
||||||
|
**resmgr=NULL fallback:** When `resmgr` is NULL (as called by the
|
||||||
|
existing `parse_resreq_primary` entry point), the parser cannot
|
||||||
|
disambiguate resname from string_literal. In this mode, all bare
|
||||||
|
words are treated as `SEL_STR` and no type validation is performed —
|
||||||
|
the caller must validate type compatibility before evaluation. The
|
||||||
|
new entry point `parse_resreq_primary_with_resmgr` should be preferred
|
||||||
|
whenever the resource manager is available.
|
||||||
|
|
||||||
The **resmgr** (resource manager) is simply an array of resource
|
The **resmgr** (resource manager) is simply an array of resource
|
||||||
definitions — each entry maps a resource name to its type and other
|
definitions — each entry maps a resource name to its type and other
|
||||||
metadata. The API server has access to this information via the cluster
|
metadata. The API server has access to this information via the cluster
|
||||||
|
|
@ -712,6 +747,11 @@ int eval_select(const xce_select_node_t* node, const xvec_idval_t* host_resvals)
|
||||||
both operands of EQ/NE are type-compatible: either both numeric, both
|
both operands of EQ/NE are type-compatible: either both numeric, both
|
||||||
string, or a RESNAME paired with a literal whose type matches the
|
string, or a RESNAME paired with a literal whose type matches the
|
||||||
resource's declared type. See §7 for type coercion rules.
|
resource's declared type. See §7 for type coercion rules.
|
||||||
|
- **Runtime type safety:** In debug builds, `eval_select` should include
|
||||||
|
`assert()` checks on operand types for `SEL_GT`/`SEL_LT`/`SEL_GE`/`SEL_LE`
|
||||||
|
branches to verify both operands are `RES_TYPE_NUMERIC`. In production
|
||||||
|
builds, these branches return `-1` on type mismatch as a defensive fallback,
|
||||||
|
since parse-time validation should prevent such cases from reaching eval.
|
||||||
|
|
||||||
### 8.4 Thread Safety
|
### 8.4 Thread Safety
|
||||||
|
|
||||||
|
|
@ -803,8 +843,8 @@ Key properties:
|
||||||
- Parse: valid expressions, unknown resource names (expect error), type
|
- Parse: valid expressions, unknown resource names (expect error), type
|
||||||
mismatches (numeric op on string resource, expect error).
|
mismatches (numeric op on string resource, expect error).
|
||||||
- CSE: `A && B` and `B && A` produce the same `xce_select_node_t*`.
|
- CSE: `A && B` and `B && A` produce the same `xce_select_node_t*`.
|
||||||
- CSE: two calls to `makeSelectNode` with same args return same pointer,
|
- CSE: two calls to `makeSelectNode` with same args return same pointer
|
||||||
refcount increases.
|
(same interned node).
|
||||||
- Eval: numeric `>`, `<`, `==`; string `==`; bool standalone; `!`, `&&`,
|
- Eval: numeric `>`, `<`, `==`; string `==`; bool standalone; `!`, `&&`,
|
||||||
`||`; short-circuit evaluation.
|
`||`; short-circuit evaluation.
|
||||||
|
|
||||||
|
|
@ -818,8 +858,9 @@ signatures and assertions are defined before implementation code.*
|
||||||
- Add `res_type_t`, `sel_node_type_t`, `xce_select_node_t` typedef +
|
- Add `res_type_t`, `sel_node_type_t`, `xce_select_node_t` typedef +
|
||||||
struct.
|
struct.
|
||||||
- Fill `struct xce_resreq_select_` with `xce_select_node_t* root`.
|
- Fill `struct xce_resreq_select_` with `xce_select_node_t* root`.
|
||||||
- Declare `makeSelectNode`, `derefSelectNode`, `eval_select`,
|
- Declare `makeSelectNode`, `eval_select`,
|
||||||
`sel_node_table_init`, `sel_node_table_fini`.
|
`sel_node_table_init`, `sel_node_table_fini`, `gc_sweep_select_nodes`,
|
||||||
|
`gc_mark_tree`.
|
||||||
- Add `res_type_to_valtype` / `valtype_to_res_type` mapping helpers.
|
- Add `res_type_to_valtype` / `valtype_to_res_type` mapping helpers.
|
||||||
- Add `parse_resreq_primary_with_resmgr(primary, resreqString, errmsg, resmgr)`
|
- Add `parse_resreq_primary_with_resmgr(primary, resreqString, errmsg, resmgr)`
|
||||||
as a new entry point. The existing `parse_resreq_primary` calls it
|
as a new entry point. The existing `parse_resreq_primary` calls it
|
||||||
|
|
@ -843,7 +884,6 @@ signatures and assertions are defined before implementation code.*
|
||||||
- `sel_node_table_init(int nslots)` / `sel_node_table_fini()`.
|
- `sel_node_table_init(int nslots)` / `sel_node_table_fini()`.
|
||||||
- `makeSelectNode` (§5.4) — with key_add return code check for TOCTOU
|
- `makeSelectNode` (§5.4) — with key_add return code check for TOCTOU
|
||||||
safety.
|
safety.
|
||||||
- `derefSelectNode` (§5.5).
|
|
||||||
- `sel_key_of`, `sel_key_hash` helpers.
|
- `sel_key_of`, `sel_key_hash` helpers.
|
||||||
- `eval_select` (§8.3) — explicit error propagation (no C boolean ops).
|
- `eval_select` (§8.3) — explicit error propagation (no C boolean ops).
|
||||||
- `host_resval_typed`, `host_get_bool`, `host_get_float`,
|
- `host_resval_typed`, `host_get_bool`, `host_get_float`,
|
||||||
|
|
@ -851,7 +891,8 @@ signatures and assertions are defined before implementation code.*
|
||||||
- Mark-and-sweep GC: `gc_sweep_select_nodes()`, `gc_mark_tree()`.
|
- Mark-and-sweep GC: `gc_sweep_select_nodes()`, `gc_mark_tree()`.
|
||||||
- AST transform pipeline: flatten, const-fold (§6.2–6.4).
|
- AST transform pipeline: flatten, const-fold (§6.2–6.4).
|
||||||
- `xce_str_match` wrapper (initially `strcmp` semantics).
|
- `xce_str_match` wrapper (initially `strcmp` semantics).
|
||||||
- Update `kill_primary_resreq` to call `derefSelectNode`.
|
- Update `kill_primary_resreq` to unlink select root (`primary->select->root = NULL`)
|
||||||
|
before freeing the select struct. GC between sessions reclaims unreachable nodes.
|
||||||
- Update `parse_resreq_primary` to call `parse_select` with resmgr.
|
- Update `parse_resreq_primary` to call `parse_select` with resmgr.
|
||||||
|
|
||||||
#### Phase 5: Build wiring
|
#### Phase 5: Build wiring
|
||||||
|
|
@ -887,16 +928,15 @@ typedef enum { RES_TYPE_NUMERIC = 0, RES_TYPE_STRING, RES_TYPE_BOOL } res_type_t
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SEL_AND, SEL_OR, SEL_NOT,
|
SEL_AND, SEL_OR, SEL_NOT,
|
||||||
SEL_GT, SEL_LT, SEL_GE, SEL_LE, SEL_EQ, SEL_NE,
|
SEL_GT, SEL_LT, SEL_GE, SEL_LE, SEL_EQ, SEL_NE,
|
||||||
SEL_RESNAME, SEL_NUM, SEL_STR, SEL_ANY, SEL_TRUE, SEL_FALSE,
|
SEL_RESNAME, SEL_NUM, SEL_STR, SEL_BOOL_LITERAL, SEL_ANY, SEL_TRUE, SEL_FALSE,
|
||||||
} sel_node_type_t;
|
} sel_node_type_t;
|
||||||
|
|
||||||
typedef struct xce_select_node_ xce_select_node_t;
|
typedef struct xce_select_node_ xce_select_node_t;
|
||||||
|
|
||||||
struct xce_select_node_ {
|
struct xce_select_node_ {
|
||||||
sel_node_type_t type;
|
sel_node_type_t type;
|
||||||
atomic_ulong refcount;
|
|
||||||
uint32_t hashcode;
|
uint32_t hashcode;
|
||||||
unsigned gc_mark; /* set during mark-and-sweep GC (§5.6) */
|
unsigned gc_mark; /* set during mark-and-sweep GC (§5.5) */
|
||||||
union {
|
union {
|
||||||
struct { xce_select_node_t *left, *right; } binop;
|
struct { xce_select_node_t *left, *right; } binop;
|
||||||
xce_select_node_t* operand;
|
xce_select_node_t* operand;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue