Repository navigation
Split Layer 2 into language-agnostic compile() + C++ render step (unlocks non-C++ backends) #18
Description
Summary
ConstitutiveModel::emit_compute_function() (include/numsim_codegen/recipe.h:209-241) returns rendered C++ source. The CSE state, the typed expression DAG, and the per-output RHS exist in structured form internally, but the entry point hard-commits to C++ rendering.
This locks out every non-C++ backend:
- FEniCS/UFL wants the typed DAG (UFL operator forms, not
auto t5 = lam * t3;). - JAX/Python wants Python with
jnp.einsum. - MLIR wants SSA values.
- LaTeX/SymPy export (useful for verification) wants the AST.
None of these can consume the rendered C++ string. Without this split, the multi-backend ambition stated in Target::target_name() cannot extend beyond C++ frameworks (MOOSE, Abaqus, Ansys, LS-DYNA per existing issues #2/#3/#4).
Where
- The string-rendering step is buried inside
recipe.h:render_compute_function()(recipe.h:287-336). - The structured form already exists:
CodeGenContext::statements()returnsstd::vector<Statement>; per-output RHS strings are separated before being concatenated.
Proposed design
Split Layer 2 into two stages:
struct CompiledModel { // language-agnostic
struct ArgSpec {
std::string name;
SymbolDecl::Kind kind;
std::size_t dim;
std::size_t rank;
Role role;
bool is_output;
};
struct OutputBinding {
std::string name;
// Reference to a leaf node in the DAG OR the final rendered RHS;
// see "decision points" below.
};
std::vector<Statement> body; // CSE-ordered
std::vector<OutputBinding> outputs;
std::vector<ArgSpec> arguments;
std::string function_name;
};
class ConstitutiveModel {
public:
[[nodiscard]] CompiledModel compile() const; // CSE + validate, no render
[[nodiscard]] std::string emit_compute_function() const {
return cxx_render(compile()); // existing behavior preserved
}
// ...
};
// C++ rendering moves into a free function (or a small renderer class):
std::string cxx_render(CompiledModel const &);Non-C++ backends consume compile() directly and supply their own renderer (ufl_render, python_render, mlir_render, ...).
Decision points
Statement::rhsis still a C++ string today. For full non-C++ support, the DAG node should travel alongside the rhs so backends can re-walk it. Either keepStatementcarrying the typed node and let backends re-render, or commit to one IR.ArgSpecordering. This issue also fixes MOOSE call-site argument order diverges from compute function signature #6 (call-site arg order mismatch) —argument_orderbecomes the single source of truth.- Where does CSE live? Probably still in
compile()since CSE is language-agnostic, but a backend that wants a different CSE strategy (e.g. fewer temporaries for inlined GPU code) might want a hook.
Scope
This is the biggest single refactor in the backlog. Touches recipe.h, all three emitters, both shipped targets, and probably the tests. Multi-day work. Don't attempt before:
- Operation registry (the new flexibility issue, see issue list) — registry-dispatched emitters are easier to make work over an IR than the current virtual-dispatch visitors.
- MOOSE call-site argument order diverges from compute function signature #6 (arg-order fix) — gets simpler when
ArgSpecis the source of truth.
Defer until backend #3 (Abaqus / Ansys / LS-DYNA, #2/#3/#4) lands or a non-C++ backend is actively planned. Until then, file this and don't ship more C++-only assumptions.
Refs
- Promoted from the Code review follow-up: Med & Low findings (umbrella) #14 umbrella (was ARCH-C1)
- Related: MOOSE call-site argument order diverges from compute function signature #6 (arg-order), the operation-registry issue
- Severity: High for the multi-backend ambition; Med if the only planned backends are C++ FEM frameworks
Activity