Skip to content

Commit 18ababb

Browse files
tbitcsoz-agent
andcommitted
fix(emit_c): use NULL directly when no expressions (static initializer constexpr)
A C static initializer can only use constant expressions. An array name (e.g. model_expressions[]) is a constant address and legal; but a pointer variable (const expr_def *ptr = NULL) is NOT a constant expression and cannot appear as a field value in a static struct initializer. Fix: when model.expressions is empty, emit nothing for the array and use NULL literally in the ARBITER_generated_model struct initializer. When expressions exist, emit the model_expressions[] array as before. Also regenerated all 19 arbiter_model.{c,h} files with the fixed emitter. Test added: test_emit_c_no_exprs_uses_null_directly verifies that empty- expression models emit '.expressions = NULL' and no array declaration. Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent cc8f3b9 commit 18ababb

4 files changed

Lines changed: 15 additions & 14 deletions

File tree

python/arbiter/emit_c.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,9 @@ def emit_c_source(model: CanonicalModel, header_name: str = "arbiter_model.h",
219219
lines.append("};")
220220
lines.append("")
221221

222-
# Expressions table
222+
# Expressions table — only emit the array when there are entries.
223+
# Never emit a pointer variable: a pointer's value is not a constant
224+
# expression in C, so it cannot be used in a static struct initializer.
223225
expressions = getattr(model, "expressions", [])
224226
if expressions:
225227
lines.append("static const struct ARBITER_expr_def model_expressions[] = {")
@@ -235,9 +237,8 @@ def emit_c_source(model: CanonicalModel, header_name: str = "arbiter_model.h",
235237
f".scale = {e['scale']} }},"
236238
)
237239
lines.append("};")
238-
else:
239-
lines.append("static const struct ARBITER_expr_def *model_expressions = NULL;")
240-
lines.append("")
240+
lines.append("")
241+
# If no expressions, we emit nothing here and use NULL directly below.
241242

242243
# Mode names
243244
if model.modes and emit_trace_strings:
@@ -257,6 +258,7 @@ def emit_c_source(model: CanonicalModel, header_name: str = "arbiter_model.h",
257258
schema_bytes = ", ".join(f"0x{schema_hex[j:j+2]}" for j in range(0, 64, 2))
258259

259260
expr_count_total = len(getattr(model, "expressions", []))
261+
exprs_field = "model_expressions" if expressions else "NULL"
260262
lines.extend([
261263
"const struct ARBITER_model ARBITER_generated_model = {",
262264
f'\t.name = "{model.name}",',
@@ -272,7 +274,7 @@ def emit_c_source(model: CanonicalModel, header_name: str = "arbiter_model.h",
272274
"\t.rules = model_rules,",
273275
"\t.conditions = model_conditions,",
274276
"\t.actions = model_actions,",
275-
"\t.expressions = model_expressions,",
277+
f"\t.expressions = {exprs_field},",
276278
"\t.mode_names = model_mode_names,",
277279
"};",
278280
"",

samples/battery_policy/src/arbiter_model.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ static const struct ARBITER_rule_def model_rules[] = {
3232
{ .id = 3, .rule_class = ARBITER_RULE_SAFETY_GUARD, .condition_start = 4, .condition_count = 2, .action_start = 0, .action_count = 1, .expr_start = 0, .expr_count = 0, .safety_goal_id = UINT16_MAX, .set_mode = 4, .safety_critical = true, .name = "rule.thermal_shutdown", .explanation = "Temperature out of safe range." },
3333
};
3434

35-
static const struct ARBITER_expr_def *model_expressions = NULL;
36-
3735
static const char *model_mode_names[] = {
3836
"mode.charging",
3937
"mode.critical",
@@ -56,6 +54,6 @@ const struct ARBITER_model ARBITER_generated_model = {
5654
.rules = model_rules,
5755
.conditions = model_conditions,
5856
.actions = model_actions,
59-
.expressions = model_expressions,
57+
.expressions = NULL,
6058
.mode_names = model_mode_names,
6159
};

samples/hydraulic_press/src/arbiter_model.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ static const struct ARBITER_rule_def model_rules[] = {
5656
{ .id = 7, .rule_class = ARBITER_RULE_SAFETY_GUARD, .condition_start = 14, .condition_count = 2, .action_start = 1, .action_count = 1, .expr_start = 0, .expr_count = 0, .safety_goal_id = UINT16_MAX, .set_mode = 5, .safety_critical = true, .name = "rule.twohand_release", .explanation = "Two-hand control released — operator may be in zone." },
5757
};
5858

59-
static const struct ARBITER_expr_def *model_expressions = NULL;
60-
6159
static const char *model_mode_names[] = {
6260
"mode.approach",
6361
"mode.estop",
@@ -85,6 +83,6 @@ const struct ARBITER_model ARBITER_generated_model = {
8583
.rules = model_rules,
8684
.conditions = model_conditions,
8785
.actions = model_actions,
88-
.expressions = model_expressions,
86+
.expressions = NULL,
8987
.mode_names = model_mode_names,
9088
};

tests/python/test_emit_c.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,9 @@ def test_emit_c_rules_have_expr_start_and_count():
200200
assert ".expr_count = 1" in src
201201

202202

203-
def test_emit_c_no_exprs_emits_null_pointer():
204-
"""A model with no compute expressions should emit a NULL pointer."""
203+
def test_emit_c_no_exprs_uses_null_directly():
204+
"""When there are no expressions, the model struct must use NULL directly
205+
(not a pointer variable — pointer variables are not constant expressions)."""
205206
data = {
206207
"arb_version": 0.1,
207208
"model": "no_exprs",
@@ -215,7 +216,9 @@ def test_emit_c_no_exprs_emits_null_pointer():
215216
}
216217
model = canonicalize(data)
217218
src = emit_c_source(model)
218-
assert "model_expressions = NULL" in src
219+
# The array symbol must not be declared when empty (pointer var is not constexpr).
220+
assert "model_expressions[]" not in src
221+
assert ".expressions = NULL" in src
219222
assert ".expr_count = 0" in src
220223

221224

0 commit comments

Comments
 (0)