use crate::symbolic::codegen::c_backend::codegen_c_aot_build::CAotCompileConfig;
use crate::symbolic::codegen::c_backend::codegen_c_aot_registry::register_c_build_in_registry;
use crate::symbolic::codegen::c_backend::codegen_c_aot_runtime_link::{
register_generated_c_dense_backend, register_generated_c_residual_backend,
register_generated_c_sparse_backend,
};
use crate::symbolic::codegen::codegen_aot_driver::{
generated_aot_artifact_from_prepared_problem, generated_aot_build_request_from_artifact,
AotBuildPreset, AotCodegenBackend, ExecutedGeneratedAotBuild, GeneratedAotBuildRequest,
GeneratedAotBuildResult,
};
use crate::symbolic::codegen::codegen_aot_resolution::{AotResolutionStatus, AotResolver};
use crate::symbolic::codegen::codegen_aot_runtime_link::{
register_generated_dense_cdylib_backend, register_generated_residual_cdylib_backend,
register_generated_sparse_cdylib_backend, resolve_linked_dense_backend,
resolve_linked_residual_backend, resolve_linked_sparse_backend, LinkedSparseAotBackend,
};
use crate::symbolic::codegen::codegen_provider_api::{
BackendKind, MatrixBackend, PreparedProblem, PreparedSparseProblem,
};
use crate::symbolic::codegen::codegen_runtime_api::SparseJacobianStructure;
use crate::symbolic::codegen::codegen_tasks::{
IvpResidualTask, SparseChunkingStrategy, SparseExprEntry, SparseJacobianTask,
};
use crate::symbolic::codegen::rust_backend::codegen_aot_build::AotBuildProfile;
use crate::symbolic::codegen::zig_backend::codegen_zig_aot_registry::register_zig_build_in_registry;
use crate::symbolic::codegen::zig_backend::codegen_zig_aot_runtime_link::{
register_generated_zig_dense_backend, register_generated_zig_residual_backend,
register_generated_zig_sparse_backend,
};
use crate::symbolic::symbolic_ivp::{
prepare_symbolic_ivp_problem, prepare_symbolic_ivp_residual_problem, IvpBackendError,
PreparedSymbolicIvpProblem, PreparedSymbolicIvpResidualProblem,
SymbolicIvpAotOptions, SymbolicIvpProblemOptions,
};
use crate::symbolic::symbolic_ivp_aot::{
generated_aot_artifact_from_symbolic_ivp_problem,
generated_aot_artifact_from_symbolic_ivp_residual_problem,
};
use log::{info, warn};
use std::fmt;
use std::path::{Path, PathBuf};
use std::thread::sleep;
use std::time::Duration;
#[derive(Debug, Clone, Default)]
pub struct IvpBackendStatistics {
pub backend_prepare_calls: usize,
pub backend_prepare_ms_total: f64,
pub solve_calls: usize,
pub solve_ms_total: f64,
pub step_calls: usize,
pub nonlinear_solve_calls: usize,
pub nonlinear_iterations_total: usize,
pub residual_calls: usize,
pub residual_ms_total: f64,
pub jacobian_calls: usize,
pub jacobian_ms_total: f64,
pub bdf_nfev_total: usize,
pub bdf_njev_total: usize,
pub bdf_nlu_total: usize,
}
impl IvpBackendStatistics {
pub fn record_backend_prepare_duration(&mut self, duration: Duration) {
self.backend_prepare_calls += 1;
self.backend_prepare_ms_total += duration.as_secs_f64() * 1_000.0;
}
pub fn record_solve_duration(&mut self, duration: Duration) {
self.solve_calls += 1;
self.solve_ms_total += duration.as_secs_f64() * 1_000.0;
}
pub fn record_residual_duration(&mut self, duration: Duration) {
self.residual_calls += 1;
self.residual_ms_total += duration.as_secs_f64() * 1_000.0;
}
pub fn record_jacobian_duration(&mut self, duration: Duration) {
self.jacobian_calls += 1;
self.jacobian_ms_total += duration.as_secs_f64() * 1_000.0;
}
pub fn avg_residual_ms(&self) -> Option<f64> {
(self.residual_calls > 0).then(|| self.residual_ms_total / self.residual_calls as f64)
}
pub fn avg_jacobian_ms(&self) -> Option<f64> {
(self.jacobian_calls > 0).then(|| self.jacobian_ms_total / self.jacobian_calls as f64)
}
pub fn avg_nonlinear_iterations(&self) -> Option<f64> {
(self.nonlinear_solve_calls > 0)
.then(|| self.nonlinear_iterations_total as f64 / self.nonlinear_solve_calls as f64)
}
pub fn table_report(&self) -> String {
format!(
"prepare_calls={} prepare_ms_total={:.3} solve_calls={} solve_ms_total={:.3} steps={} nonlinear_solves={} nonlinear_iters_total={} nonlinear_iters_avg={:.3} residual_calls={} residual_ms_total={:.3} residual_ms_avg={:.6} jacobian_calls={} jacobian_ms_total={:.3} jacobian_ms_avg={:.6} bdf[nfev/njev/nlu]={}/{}/{}",
self.backend_prepare_calls,
self.backend_prepare_ms_total,
self.solve_calls,
self.solve_ms_total,
self.step_calls,
self.nonlinear_solve_calls,
self.nonlinear_iterations_total,
self.avg_nonlinear_iterations().unwrap_or(0.0),
self.residual_calls,
self.residual_ms_total,
self.avg_residual_ms().unwrap_or(0.0),
self.jacobian_calls,
self.jacobian_ms_total,
self.avg_jacobian_ms().unwrap_or(0.0),
self.bdf_nfev_total,
self.bdf_njev_total,
self.bdf_nlu_total,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DenseIvpGeneratedBackendMode {
#[default]
Defaults,
RequirePrebuilt,
BuildIfMissingRelease,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SymbolicIvpAotBuildPolicy {
#[default]
UseIfAvailable,
RequirePrebuilt,
BuildIfMissing { profile: AotBuildProfile },
RebuildAlways { profile: AotBuildProfile },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectedSymbolicIvpBackendKind {
Lambdify,
AotCompiled,
AotRegisteredButNotBuilt,
AotMissing,
}
#[derive(Debug, Clone)]
pub struct SymbolicIvpGeneratedBackendConfig {
pub resolver: Option<AotResolver>,
pub aot_options: SymbolicIvpAotOptions,
pub sparse_jacobian_chunking_strategy: SparseChunkingStrategy,
pub build_policy: SymbolicIvpAotBuildPolicy,
pub aot_codegen_backend: AotCodegenBackend,
pub aot_c_compiler: Option<String>,
pub output_parent_dir: Option<PathBuf>,
pub crate_name_override: Option<String>,
pub module_name_override: Option<String>,
}
impl Default for SymbolicIvpGeneratedBackendConfig {
fn default() -> Self {
Self {
resolver: None,
aot_options: SymbolicIvpAotOptions::default(),
sparse_jacobian_chunking_strategy: SparseChunkingStrategy::Whole,
build_policy: SymbolicIvpAotBuildPolicy::default(),
aot_codegen_backend: AotCodegenBackend::default(),
aot_c_compiler: None,
output_parent_dir: None,
crate_name_override: None,
module_name_override: None,
}
}
}
impl SymbolicIvpGeneratedBackendConfig {
pub fn new() -> Self {
Self::default()
}
pub fn defaults() -> Self {
Self::new()
}
pub fn require_prebuilt() -> Self {
Self::new().with_build_policy(SymbolicIvpAotBuildPolicy::RequirePrebuilt)
}
pub fn build_if_missing_release(output_parent_dir: impl Into<PathBuf>) -> Self {
Self::new()
.with_output_parent_dir(Some(output_parent_dir.into()))
.with_build_policy(SymbolicIvpAotBuildPolicy::BuildIfMissing {
profile: AotBuildProfile::Release,
})
.with_c_gcc()
}
pub fn from_mode(mode: DenseIvpGeneratedBackendMode) -> Self {
match mode {
DenseIvpGeneratedBackendMode::Defaults => Self::defaults(),
DenseIvpGeneratedBackendMode::RequirePrebuilt => Self::require_prebuilt(),
DenseIvpGeneratedBackendMode::BuildIfMissingRelease => Self::new()
.with_build_policy(SymbolicIvpAotBuildPolicy::BuildIfMissing {
profile: AotBuildProfile::Release,
})
.with_c_gcc(),
}
}
pub fn with_resolver(mut self, resolver: Option<AotResolver>) -> Self {
self.resolver = resolver;
self
}
pub fn with_aot_options(mut self, aot_options: SymbolicIvpAotOptions) -> Self {
self.aot_options = aot_options;
self
}
pub fn with_sparse_jacobian_chunking_strategy(
mut self,
sparse_jacobian_chunking_strategy: SparseChunkingStrategy,
) -> Self {
self.sparse_jacobian_chunking_strategy = sparse_jacobian_chunking_strategy;
self
}
pub fn with_build_policy(mut self, build_policy: SymbolicIvpAotBuildPolicy) -> Self {
self.build_policy = build_policy;
self
}
pub fn with_aot_codegen_backend(mut self, backend: AotCodegenBackend) -> Self {
self.aot_codegen_backend = backend;
self
}
pub fn with_aot_c_compiler(mut self, compiler: impl Into<String>) -> Self {
self.aot_c_compiler = Some(compiler.into());
self
}
pub fn with_output_parent_dir(mut self, output_parent_dir: Option<PathBuf>) -> Self {
self.output_parent_dir = output_parent_dir;
self
}
pub fn with_crate_name_override(mut self, crate_name_override: Option<String>) -> Self {
self.crate_name_override = crate_name_override;
self
}
pub fn with_module_name_override(mut self, module_name_override: Option<String>) -> Self {
self.module_name_override = module_name_override;
self
}
fn output_parent_dir(&self) -> Result<&Path, SymbolicIvpGeneratedError> {
self.output_parent_dir
.as_deref()
.ok_or(SymbolicIvpGeneratedError::AotBuildOutputDirMissing)
}
pub fn with_c_tcc(self) -> Self {
self.with_aot_codegen_backend(AotCodegenBackend::C)
.with_aot_c_compiler("tcc")
}
pub fn with_c_gcc(self) -> Self {
self.with_aot_codegen_backend(AotCodegenBackend::C)
.with_aot_c_compiler("gcc")
}
pub fn for_repeated_solves(self) -> Self {
self.with_c_gcc()
}
pub fn with_rust(self) -> Self {
let mut config = self.with_aot_codegen_backend(AotCodegenBackend::Rust);
config.aot_c_compiler = None;
config
}
pub fn with_zig(self) -> Self {
let mut config = self.with_aot_codegen_backend(AotCodegenBackend::Zig);
config.aot_c_compiler = None;
config
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SymbolicIvpGeneratedError {
IvpBackend(IvpBackendError),
CompiledAotArtifactMissing(String),
CompiledAotArtifactNotBuilt(String),
CompiledAotRuntimeUnavailable(String),
AotBuildOutputDirMissing,
AotBuildFailed(String),
}
impl fmt::Display for SymbolicIvpGeneratedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IvpBackend(err) => write!(f, "{err}"),
Self::CompiledAotArtifactMissing(message)
| Self::CompiledAotArtifactNotBuilt(message)
| Self::CompiledAotRuntimeUnavailable(message)
| Self::AotBuildFailed(message) => write!(f, "{message}"),
Self::AotBuildOutputDirMissing => {
write!(
f,
"symbolic IVP generated backend build requested without output directory"
)
}
}
}
}
impl std::error::Error for SymbolicIvpGeneratedError {}
impl From<IvpBackendError> for SymbolicIvpGeneratedError {
fn from(value: IvpBackendError) -> Self {
Self::IvpBackend(value)
}
}
fn executed_process_output(executed: &ExecutedGeneratedAotBuild) -> (Option<i32>, String, String) {
match executed {
ExecutedGeneratedAotBuild::Rust(result) => (
result.status_code,
result.stdout.clone(),
result.stderr.clone(),
),
ExecutedGeneratedAotBuild::C(result) => (
result.status_code,
result.stdout.clone(),
result.stderr.clone(),
),
ExecutedGeneratedAotBuild::Zig(result) => (
result.status_code,
result.stdout.clone(),
result.stderr.clone(),
),
}
}
fn is_transient_aot_infra_failure(text: &str) -> bool {
let low = text.to_ascii_lowercase();
low.contains("permission denied")
|| low.contains("access is denied")
|| low.contains("being used by another process")
|| low.contains("resource busy")
|| low.contains("temporarily unavailable")
|| low.contains("failed to spawn build runner")
|| low.contains("could not write")
|| low.contains("file is locked")
|| low.contains("sharing violation")
}
fn execute_generated_build_with_retry(
build: &GeneratedAotBuildResult,
build_context: &str,
) -> Result<ExecutedGeneratedAotBuild, SymbolicIvpGeneratedError> {
const MAX_ATTEMPTS: usize = 3;
let mut last_failure: Option<String> = None;
for attempt in 1..=MAX_ATTEMPTS {
match build.execute() {
Ok(executed) => {
if executed.succeeded() {
return Ok(executed);
}
let (status, stdout, stderr) = executed_process_output(&executed);
let detail = format!("status={status:?}\nstdout:\n{stdout}\nstderr:\n{stderr}");
let transient = is_transient_aot_infra_failure(&detail);
last_failure = Some(detail);
if transient && attempt < MAX_ATTEMPTS {
sleep(Duration::from_millis((attempt as u64) * 120));
continue;
}
}
Err(err) => {
let detail = err.to_string();
let transient = is_transient_aot_infra_failure(&detail);
last_failure = Some(detail);
if transient && attempt < MAX_ATTEMPTS {
sleep(Duration::from_millis((attempt as u64) * 120));
continue;
}
}
}
break;
}
let detail = last_failure.unwrap_or_else(|| "unknown build failure".to_string());
Err(SymbolicIvpGeneratedError::AotBuildFailed(format!(
"AOT build execution failed ({build_context}) after retry policy; detail:\n{detail}"
)))
}
pub struct PreparedGeneratedSymbolicIvpProblem {
pub problem: PreparedSymbolicIvpProblem,
pub selected_backend: SelectedSymbolicIvpBackendKind,
pub updated_resolver: Option<AotResolver>,
pub build_result: Option<GeneratedAotBuildResult>,
}
impl PreparedGeneratedSymbolicIvpProblem {
pub fn into_problem(self) -> PreparedSymbolicIvpProblem {
self.problem
}
}
pub struct PreparedGeneratedSymbolicIvpResidualProblem {
pub problem: PreparedSymbolicIvpResidualProblem,
pub selected_backend: SelectedSymbolicIvpBackendKind,
pub updated_resolver: Option<AotResolver>,
pub build_result: Option<GeneratedAotBuildResult>,
}
impl PreparedGeneratedSymbolicIvpResidualProblem {
pub fn into_problem(self) -> PreparedSymbolicIvpResidualProblem {
self.problem
}
}
pub struct PreparedGeneratedSymbolicIvpSparseBackend {
pub problem_key: String,
pub selected_backend: SelectedSymbolicIvpBackendKind,
pub linked_backend: Option<LinkedSparseAotBackend>,
pub jacobian_structure: SparseJacobianStructure,
pub updated_resolver: Option<AotResolver>,
pub build_result: Option<GeneratedAotBuildResult>,
}
fn generated_names(
problem_key: &str,
config: &SymbolicIvpGeneratedBackendConfig,
) -> (String, String) {
let suffix = problem_key
.chars()
.take(16)
.collect::<String>()
.replace('-', "_");
let crate_name = config
.crate_name_override
.clone()
.unwrap_or_else(|| format!("generated_ivp_dense_{suffix}"));
let module_name = config
.module_name_override
.clone()
.unwrap_or_else(|| format!("generated_ivp_dense_module_{suffix}"));
(crate_name, module_name)
}
fn generated_residual_names(
problem_key: &str,
config: &SymbolicIvpGeneratedBackendConfig,
) -> (String, String) {
let suffix = problem_key
.chars()
.take(16)
.collect::<String>()
.replace('-', "_");
let crate_name = config
.crate_name_override
.clone()
.unwrap_or_else(|| format!("generated_ivp_residual_{suffix}"));
let module_name = config
.module_name_override
.clone()
.unwrap_or_else(|| format!("generated_ivp_residual_module_{suffix}"));
(crate_name, module_name)
}
fn generated_sparse_names(
problem_key: &str,
config: &SymbolicIvpGeneratedBackendConfig,
) -> (String, String) {
let suffix = problem_key
.chars()
.take(16)
.collect::<String>()
.replace('-', "_");
let crate_name = config
.crate_name_override
.clone()
.unwrap_or_else(|| format!("generated_ivp_sparse_{suffix}"));
let module_name = config
.module_name_override
.clone()
.unwrap_or_else(|| format!("generated_ivp_sparse_module_{suffix}"));
(crate_name, module_name)
}
fn select_backend(
problem: &PreparedSymbolicIvpProblem,
resolver: Option<&AotResolver>,
options: SymbolicIvpAotOptions,
) -> SelectedSymbolicIvpBackendKind {
let prepared = problem.prepare_dense_aot_problem(options);
let problem_key = prepared.problem_key();
if let Some(linked) = resolve_linked_dense_backend(problem_key.as_str()) {
if linked.problem_key == problem_key {
return SelectedSymbolicIvpBackendKind::AotCompiled;
}
}
match resolver {
Some(resolver) => match resolver.resolve_by_problem_key(problem_key.as_str()).status {
AotResolutionStatus::Missing => SelectedSymbolicIvpBackendKind::AotMissing,
AotResolutionStatus::RegisteredButNotBuilt => {
SelectedSymbolicIvpBackendKind::AotRegisteredButNotBuilt
}
AotResolutionStatus::Compiled => SelectedSymbolicIvpBackendKind::AotCompiled,
},
None => SelectedSymbolicIvpBackendKind::AotMissing,
}
}
fn select_residual_backend(
problem: &PreparedSymbolicIvpResidualProblem,
resolver: Option<&AotResolver>,
options: SymbolicIvpAotOptions,
) -> SelectedSymbolicIvpBackendKind {
let prepared = problem.prepare_residual_aot_problem(options);
let problem_key = prepared.problem_key();
if let Some(linked) = resolve_linked_residual_backend(problem_key.as_str()) {
if linked.problem_key == problem_key {
return SelectedSymbolicIvpBackendKind::AotCompiled;
}
}
match resolver {
Some(resolver) => match resolver.resolve_by_problem_key(problem_key.as_str()).status {
AotResolutionStatus::Missing => SelectedSymbolicIvpBackendKind::AotMissing,
AotResolutionStatus::RegisteredButNotBuilt => {
SelectedSymbolicIvpBackendKind::AotRegisteredButNotBuilt
}
AotResolutionStatus::Compiled => SelectedSymbolicIvpBackendKind::AotCompiled,
},
None => SelectedSymbolicIvpBackendKind::AotMissing,
}
}
fn select_sparse_backend(
problem: &PreparedSparseProblem<'_>,
resolver: Option<&AotResolver>,
) -> SelectedSymbolicIvpBackendKind {
let problem_key = crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest::from(
problem,
)
.problem_key();
if let Some(linked) = resolve_linked_sparse_backend(problem_key.as_str()) {
if linked.problem_key == problem_key {
return SelectedSymbolicIvpBackendKind::AotCompiled;
}
}
match resolver {
Some(resolver) => match resolver.resolve_by_problem_key(problem_key.as_str()).status {
AotResolutionStatus::Missing => SelectedSymbolicIvpBackendKind::AotMissing,
AotResolutionStatus::RegisteredButNotBuilt => {
SelectedSymbolicIvpBackendKind::AotRegisteredButNotBuilt
}
AotResolutionStatus::Compiled => SelectedSymbolicIvpBackendKind::AotCompiled,
},
None => SelectedSymbolicIvpBackendKind::AotMissing,
}
}
fn should_build_for_selection(
config: &SymbolicIvpGeneratedBackendConfig,
selected_backend: SelectedSymbolicIvpBackendKind,
) -> bool {
match config.build_policy {
SymbolicIvpAotBuildPolicy::UseIfAvailable | SymbolicIvpAotBuildPolicy::RequirePrebuilt => {
false
}
SymbolicIvpAotBuildPolicy::BuildIfMissing { .. } => {
selected_backend != SelectedSymbolicIvpBackendKind::AotCompiled
}
SymbolicIvpAotBuildPolicy::RebuildAlways { .. } => true,
}
}
fn build_profile(policy: SymbolicIvpAotBuildPolicy) -> Option<AotBuildProfile> {
match policy {
SymbolicIvpAotBuildPolicy::BuildIfMissing { profile }
| SymbolicIvpAotBuildPolicy::RebuildAlways { profile } => Some(profile),
SymbolicIvpAotBuildPolicy::UseIfAvailable | SymbolicIvpAotBuildPolicy::RequirePrebuilt => {
None
}
}
}
fn build_preset(policy: SymbolicIvpAotBuildPolicy) -> Option<AotBuildPreset> {
match build_profile(policy) {
Some(AotBuildProfile::Debug) => Some(AotBuildPreset::DevFastest),
Some(AotBuildProfile::Release) => Some(AotBuildPreset::Production),
None => None,
}
}
fn register_ivp_build_result_in_registry(
resolver_snapshot: Option<AotResolver>,
manifest: crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest,
build: &GeneratedAotBuildResult,
) -> Result<AotResolver, SymbolicIvpGeneratedError> {
let mut registry = resolver_snapshot
.as_ref()
.map(|resolver| resolver.registry().clone())
.unwrap_or_default();
match build {
GeneratedAotBuildResult::Rust(result) => {
registry.register_materialized_build(manifest, result);
}
GeneratedAotBuildResult::C(result) => {
register_c_build_in_registry(&mut registry, manifest, result);
}
GeneratedAotBuildResult::Zig(result) => {
register_zig_build_in_registry(&mut registry, manifest, result);
}
}
Ok(AotResolver::new(registry))
}
fn register_ivp_runtime_backend(
backend: AotCodegenBackend,
resolver: &AotResolver,
problem_key: &str,
) -> Result<(), SymbolicIvpGeneratedError> {
let resolved = resolver.resolve_by_problem_key(problem_key);
match backend {
AotCodegenBackend::Rust => register_generated_dense_cdylib_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
AotCodegenBackend::C => register_generated_c_dense_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
AotCodegenBackend::Zig => register_generated_zig_dense_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
}
}
fn register_ivp_residual_runtime_backend(
backend: AotCodegenBackend,
resolver: &AotResolver,
problem_key: &str,
) -> Result<(), SymbolicIvpGeneratedError> {
let resolved = resolver.resolve_by_problem_key(problem_key);
match backend {
AotCodegenBackend::Rust => register_generated_residual_cdylib_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
AotCodegenBackend::C => register_generated_c_residual_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
AotCodegenBackend::Zig => register_generated_zig_residual_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
}
}
fn register_ivp_sparse_runtime_backend(
backend: AotCodegenBackend,
resolver: &AotResolver,
problem_key: &str,
) -> Result<(), SymbolicIvpGeneratedError> {
let resolved = resolver.resolve_by_problem_key(problem_key);
match backend {
AotCodegenBackend::Rust => register_generated_sparse_cdylib_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
AotCodegenBackend::C => register_generated_c_sparse_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
AotCodegenBackend::Zig => register_generated_zig_sparse_backend(&resolved.registered)
.map(|_| ())
.map_err(SymbolicIvpGeneratedError::AotBuildFailed),
}
}
fn perform_requested_build(
problem: &PreparedSymbolicIvpProblem,
config: &SymbolicIvpGeneratedBackendConfig,
resolver_snapshot: Option<AotResolver>,
) -> Result<(Option<GeneratedAotBuildResult>, Option<AotResolver>), SymbolicIvpGeneratedError> {
let preset = match build_preset(config.build_policy) {
Some(preset) => preset,
None => return Ok((None, resolver_snapshot)),
};
let prepared = problem.prepare_dense_aot_problem(config.aot_options);
let (crate_name, module_name) = generated_names(&prepared.problem_key(), config);
info!(
"Materializing symbolic IVP dense {:?} AOT build '{}' with preset {:?}",
config.aot_codegen_backend, crate_name, preset
);
let artifact = generated_aot_artifact_from_symbolic_ivp_problem(
&crate_name,
&module_name,
problem,
config.aot_options,
config.aot_codegen_backend,
);
let mut request =
generated_aot_build_request_from_artifact(artifact, config.output_parent_dir()?, preset);
if let (GeneratedAotBuildRequest::C(c_request), Some(compiler)) =
(&mut request, config.aot_c_compiler.as_ref())
{
let compile_config = match preset {
AotBuildPreset::Production => CAotCompileConfig::production(),
AotBuildPreset::FastBuild => CAotCompileConfig::fast_build(),
AotBuildPreset::DevFastest => CAotCompileConfig::dev_fastest(),
}
.with_compiler(compiler.clone());
*c_request = c_request.clone().with_compile_config(compile_config);
}
let build = request
.materialize()
.map_err(|err| SymbolicIvpGeneratedError::AotBuildFailed(err.to_string()))?;
execute_generated_build_with_retry(
&build,
&format!(
"ivp-dense backend={:?} key={}",
config.aot_codegen_backend,
prepared.problem_key()
),
)?;
let resolver =
register_ivp_build_result_in_registry(resolver_snapshot, prepared.manifest(), &build)?;
register_ivp_runtime_backend(
config.aot_codegen_backend,
&resolver,
prepared.problem_key().as_str(),
)?;
Ok((Some(build), Some(resolver)))
}
fn perform_requested_residual_build(
problem: &PreparedSymbolicIvpResidualProblem,
config: &SymbolicIvpGeneratedBackendConfig,
resolver_snapshot: Option<AotResolver>,
) -> Result<(Option<GeneratedAotBuildResult>, Option<AotResolver>), SymbolicIvpGeneratedError> {
let preset = match build_preset(config.build_policy) {
Some(preset) => preset,
None => return Ok((None, resolver_snapshot)),
};
let prepared = problem.prepare_residual_aot_problem(config.aot_options);
let (crate_name, module_name) = generated_residual_names(&prepared.problem_key(), config);
info!(
"Materializing symbolic IVP residual-only {:?} AOT build '{}' with preset {:?}",
config.aot_codegen_backend, crate_name, preset
);
let artifact = generated_aot_artifact_from_symbolic_ivp_residual_problem(
&crate_name,
&module_name,
problem,
config.aot_options,
config.aot_codegen_backend,
);
let mut request =
generated_aot_build_request_from_artifact(artifact, config.output_parent_dir()?, preset);
if let (GeneratedAotBuildRequest::C(c_request), Some(compiler)) =
(&mut request, config.aot_c_compiler.as_ref())
{
let compile_config = match preset {
AotBuildPreset::Production => CAotCompileConfig::production(),
AotBuildPreset::FastBuild => CAotCompileConfig::fast_build(),
AotBuildPreset::DevFastest => CAotCompileConfig::dev_fastest(),
}
.with_compiler(compiler.clone());
*c_request = c_request.clone().with_compile_config(compile_config);
}
let build = request
.materialize()
.map_err(|err| SymbolicIvpGeneratedError::AotBuildFailed(err.to_string()))?;
execute_generated_build_with_retry(
&build,
&format!(
"ivp-residual backend={:?} key={}",
config.aot_codegen_backend,
prepared.problem_key()
),
)?;
let resolver =
register_ivp_build_result_in_registry(resolver_snapshot, prepared.manifest(), &build)?;
register_ivp_residual_runtime_backend(
config.aot_codegen_backend,
&resolver,
prepared.problem_key().as_str(),
)?;
Ok((Some(build), Some(resolver)))
}
fn perform_requested_sparse_build(
problem: &PreparedSparseProblem<'_>,
config: &SymbolicIvpGeneratedBackendConfig,
resolver_snapshot: Option<AotResolver>,
) -> Result<(Option<GeneratedAotBuildResult>, Option<AotResolver>), SymbolicIvpGeneratedError> {
let preset = match build_preset(config.build_policy) {
Some(preset) => preset,
None => return Ok((None, resolver_snapshot)),
};
let manifest = crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest::from(problem);
let problem_key = manifest.problem_key();
let (crate_name, module_name) = generated_sparse_names(&problem_key, config);
info!(
"Materializing symbolic IVP sparse {:?} AOT build '{}' with preset {:?}",
config.aot_codegen_backend, crate_name, preset
);
let prepared_problem = PreparedProblem::sparse(problem.clone());
let artifact = generated_aot_artifact_from_prepared_problem(
&crate_name,
&module_name,
&prepared_problem,
config.aot_codegen_backend,
);
let mut request =
generated_aot_build_request_from_artifact(artifact, config.output_parent_dir()?, preset);
if let (GeneratedAotBuildRequest::C(c_request), Some(compiler)) =
(&mut request, config.aot_c_compiler.as_ref())
{
let compile_config = match preset {
AotBuildPreset::Production => CAotCompileConfig::production(),
AotBuildPreset::FastBuild => CAotCompileConfig::fast_build(),
AotBuildPreset::DevFastest => CAotCompileConfig::dev_fastest(),
}
.with_compiler(compiler.clone());
*c_request = c_request.clone().with_compile_config(compile_config);
}
let build = request
.materialize()
.map_err(|err| SymbolicIvpGeneratedError::AotBuildFailed(err.to_string()))?;
execute_generated_build_with_retry(
&build,
&format!(
"ivp-sparse backend={:?} key={}",
config.aot_codegen_backend, problem_key
),
)?;
let resolver = register_ivp_build_result_in_registry(resolver_snapshot, manifest, &build)?;
register_ivp_sparse_runtime_backend(config.aot_codegen_backend, &resolver, problem_key.as_str())?;
Ok((Some(build), Some(resolver)))
}
pub fn prepare_generated_symbolic_ivp_sparse_backend(
equations: Vec<crate::symbolic::symbolic_engine::Expr>,
variables: Vec<String>,
time_arg: String,
options: SymbolicIvpProblemOptions,
config: SymbolicIvpGeneratedBackendConfig,
) -> Result<PreparedGeneratedSymbolicIvpSparseBackend, SymbolicIvpGeneratedError> {
let baseline_problem = prepare_symbolic_ivp_problem(equations, variables, time_arg, options)?;
let variable_refs = baseline_problem
.variables
.iter()
.map(|value| value.as_str())
.collect::<Vec<_>>();
let parameter_refs = baseline_problem.equation_parameters.as_ref().map(|parameters| {
parameters
.iter()
.map(|value| value.as_str())
.collect::<Vec<_>>()
});
let mut sparse_param_refs = Vec::with_capacity(1 + parameter_refs.as_ref().map_or(0, Vec::len));
sparse_param_refs.push(baseline_problem.time_arg.as_str());
if let Some(params) = parameter_refs.as_ref() {
sparse_param_refs.extend(params.iter().copied());
}
let sparse_entries = baseline_problem
.symbolic_jacobian
.iter()
.enumerate()
.flat_map(|(row, jac_row)| {
jac_row.iter().enumerate().filter_map(move |(col, expr)| {
if expr.is_zero() {
None
} else {
Some(SparseExprEntry { row, col, expr })
}
})
})
.collect::<Vec<_>>();
let residual_plan = IvpResidualTask {
fn_name: "generated_ivp_residual_eval",
time_arg: baseline_problem.time_arg.as_str(),
residuals: &baseline_problem.equations,
variables: &variable_refs,
params: parameter_refs.as_deref(),
}
.runtime_plan(config.aot_options.residual_strategy);
let shape = (
baseline_problem.symbolic_jacobian.len(),
baseline_problem
.symbolic_jacobian
.first()
.map_or(0, |row| row.len()),
);
let sparse_plan = SparseJacobianTask {
fn_name: "generated_ivp_jacobian_values_eval",
shape,
entries: &sparse_entries,
variables: &variable_refs,
params: Some(sparse_param_refs.as_slice()),
}
.runtime_plan(config.sparse_jacobian_chunking_strategy);
let jacobian_structure = sparse_plan.structure.clone();
let prepared_sparse = PreparedSparseProblem::new(
BackendKind::Aot,
MatrixBackend::SparseCol,
residual_plan,
sparse_plan,
);
let initial_selection = select_sparse_backend(&prepared_sparse, config.resolver.as_ref());
let (build_result, resolver_snapshot) =
if should_build_for_selection(&config, initial_selection) {
perform_requested_sparse_build(&prepared_sparse, &config, config.resolver.clone())?
} else {
(None, config.resolver.clone())
};
let final_selection = select_sparse_backend(&prepared_sparse, resolver_snapshot.as_ref());
let problem_key = crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest::from(
&prepared_sparse,
)
.problem_key();
let mut linked_backend = resolve_linked_sparse_backend(problem_key.as_str());
if linked_backend.is_none() {
if let Some(resolver) = resolver_snapshot.as_ref() {
let _ = register_ivp_sparse_runtime_backend(
config.aot_codegen_backend,
resolver,
problem_key.as_str(),
);
linked_backend = resolve_linked_sparse_backend(problem_key.as_str());
}
}
match final_selection {
SelectedSymbolicIvpBackendKind::AotCompiled => {
if linked_backend.is_none() {
return Err(SymbolicIvpGeneratedError::CompiledAotRuntimeUnavailable(
"symbolic IVP sparse compiled AOT artifact exists but no linked runtime is registered"
.to_string(),
));
}
Ok(PreparedGeneratedSymbolicIvpSparseBackend {
problem_key,
selected_backend: final_selection,
linked_backend,
jacobian_structure,
updated_resolver: resolver_snapshot,
build_result,
})
}
SelectedSymbolicIvpBackendKind::AotRegisteredButNotBuilt => Err(
SymbolicIvpGeneratedError::CompiledAotArtifactNotBuilt(
"symbolic IVP sparse AOT artifact is registered but not built".to_string(),
),
),
SelectedSymbolicIvpBackendKind::AotMissing | SelectedSymbolicIvpBackendKind::Lambdify => {
match config.build_policy {
SymbolicIvpAotBuildPolicy::RequirePrebuilt => Err(
SymbolicIvpGeneratedError::CompiledAotArtifactMissing(
"symbolic IVP sparse AOT artifact is missing".to_string(),
),
),
_ => Ok(PreparedGeneratedSymbolicIvpSparseBackend {
problem_key,
selected_backend: SelectedSymbolicIvpBackendKind::Lambdify,
linked_backend: None,
jacobian_structure,
updated_resolver: resolver_snapshot,
build_result,
}),
}
}
}
}
pub fn prepare_generated_symbolic_ivp_problem(
equations: Vec<crate::symbolic::symbolic_engine::Expr>,
variables: Vec<String>,
time_arg: String,
options: SymbolicIvpProblemOptions,
config: SymbolicIvpGeneratedBackendConfig,
) -> Result<PreparedGeneratedSymbolicIvpProblem, SymbolicIvpGeneratedError> {
let baseline_problem = prepare_symbolic_ivp_problem(equations, variables, time_arg, options)?;
let initial_selection = select_backend(
&baseline_problem,
config.resolver.as_ref(),
config.aot_options,
);
let (build_result, resolver_snapshot) =
if should_build_for_selection(&config, initial_selection) {
perform_requested_build(&baseline_problem, &config, config.resolver.clone())?
} else {
(None, config.resolver.clone())
};
let final_selection = select_backend(
&baseline_problem,
resolver_snapshot.as_ref(),
config.aot_options,
);
match final_selection {
SelectedSymbolicIvpBackendKind::Lambdify | SelectedSymbolicIvpBackendKind::AotMissing => {
match config.build_policy {
SymbolicIvpAotBuildPolicy::RequirePrebuilt => {
Err(SymbolicIvpGeneratedError::CompiledAotArtifactMissing(
"symbolic IVP AOT artifact is missing".to_string(),
))
}
_ => Ok(PreparedGeneratedSymbolicIvpProblem {
problem: baseline_problem,
selected_backend: SelectedSymbolicIvpBackendKind::Lambdify,
updated_resolver: resolver_snapshot,
build_result,
}),
}
}
SelectedSymbolicIvpBackendKind::AotRegisteredButNotBuilt => {
Err(SymbolicIvpGeneratedError::CompiledAotArtifactNotBuilt(
"symbolic IVP AOT artifact is registered but not built".to_string(),
))
}
SelectedSymbolicIvpBackendKind::AotCompiled => {
let prepared = baseline_problem.prepare_dense_aot_problem(config.aot_options);
let problem_key = prepared.problem_key();
if let Some(linked) = resolve_linked_dense_backend(problem_key.as_str()) {
Ok(PreparedGeneratedSymbolicIvpProblem {
problem: baseline_problem.into_linked_dense_backend(linked),
selected_backend: SelectedSymbolicIvpBackendKind::AotCompiled,
updated_resolver: resolver_snapshot,
build_result,
})
} else {
match config.build_policy {
SymbolicIvpAotBuildPolicy::RequirePrebuilt => Err(
SymbolicIvpGeneratedError::CompiledAotRuntimeUnavailable(
"symbolic IVP compiled AOT artifact exists but no linked runtime is registered"
.to_string(),
),
),
_ => {
warn!(
"Symbolic IVP compiled AOT artifact exists but no linked runtime is registered; falling back to lambdify"
);
Ok(PreparedGeneratedSymbolicIvpProblem {
problem: baseline_problem,
selected_backend: SelectedSymbolicIvpBackendKind::Lambdify,
updated_resolver: resolver_snapshot,
build_result,
})
}
}
}
}
}
}
pub fn prepare_generated_symbolic_ivp_residual_problem(
equations: Vec<crate::symbolic::symbolic_engine::Expr>,
variables: Vec<String>,
time_arg: String,
options: SymbolicIvpProblemOptions,
config: SymbolicIvpGeneratedBackendConfig,
) -> Result<PreparedGeneratedSymbolicIvpResidualProblem, SymbolicIvpGeneratedError> {
let baseline_problem =
prepare_symbolic_ivp_residual_problem(equations, variables, time_arg, options)?;
let initial_selection = select_residual_backend(
&baseline_problem,
config.resolver.as_ref(),
config.aot_options,
);
let (build_result, resolver_snapshot) =
if should_build_for_selection(&config, initial_selection) {
perform_requested_residual_build(&baseline_problem, &config, config.resolver.clone())?
} else {
(None, config.resolver.clone())
};
let final_selection = select_residual_backend(
&baseline_problem,
resolver_snapshot.as_ref(),
config.aot_options,
);
match final_selection {
SelectedSymbolicIvpBackendKind::Lambdify | SelectedSymbolicIvpBackendKind::AotMissing => {
match config.build_policy {
SymbolicIvpAotBuildPolicy::RequirePrebuilt => {
Err(SymbolicIvpGeneratedError::CompiledAotArtifactMissing(
"symbolic IVP residual-only AOT artifact is missing".to_string(),
))
}
_ => Ok(PreparedGeneratedSymbolicIvpResidualProblem {
problem: baseline_problem,
selected_backend: SelectedSymbolicIvpBackendKind::Lambdify,
updated_resolver: resolver_snapshot,
build_result,
}),
}
}
SelectedSymbolicIvpBackendKind::AotRegisteredButNotBuilt => {
Err(SymbolicIvpGeneratedError::CompiledAotArtifactNotBuilt(
"symbolic IVP residual-only AOT artifact is registered but not built".to_string(),
))
}
SelectedSymbolicIvpBackendKind::AotCompiled => {
let prepared = baseline_problem.prepare_residual_aot_problem(config.aot_options);
let problem_key = prepared.problem_key();
if let Some(linked) = resolve_linked_residual_backend(problem_key.as_str()) {
Ok(PreparedGeneratedSymbolicIvpResidualProblem {
problem: baseline_problem.into_linked_residual_backend(linked),
selected_backend: SelectedSymbolicIvpBackendKind::AotCompiled,
updated_resolver: resolver_snapshot,
build_result,
})
} else {
match config.build_policy {
SymbolicIvpAotBuildPolicy::RequirePrebuilt => Err(
SymbolicIvpGeneratedError::CompiledAotRuntimeUnavailable(
"symbolic IVP residual-only compiled AOT artifact exists but no linked runtime is registered"
.to_string(),
),
),
_ => {
warn!(
"Symbolic IVP residual-only compiled AOT artifact exists but no linked runtime is registered; falling back to lambdify"
);
Ok(PreparedGeneratedSymbolicIvpResidualProblem {
problem: baseline_problem,
selected_backend: SelectedSymbolicIvpBackendKind::Lambdify,
updated_resolver: resolver_snapshot,
build_result,
})
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbolic::codegen::codegen_aot_runtime_link::{
register_linked_dense_backend, unregister_linked_dense_backend, LinkedDenseAotBackend,
};
use crate::symbolic::symbolic_engine::Expr;
use crate::symbolic::symbolic_ivp::IvpBackendKind;
use nalgebra::DVector;
use std::sync::Arc;
use tempfile::tempdir;
fn sample_problem() -> (Vec<Expr>, Vec<String>, String, SymbolicIvpProblemOptions) {
(
vec![
Expr::parse_expression("a*t + y + b*z"),
Expr::parse_expression("c*y - z + b*t"),
],
vec!["y".to_string(), "z".to_string()],
"t".to_string(),
SymbolicIvpProblemOptions::new()
.with_equation_parameters(vec!["a".to_string(), "b".to_string(), "c".to_string()])
.with_equation_parameter_values(DVector::from_vec(vec![2.0, -0.5, 3.0])),
)
}
#[test]
fn generated_ivp_defaults_fall_back_to_lambdify_when_aot_is_missing() {
let (equations, variables, time_arg, options) = sample_problem();
let prepared = prepare_generated_symbolic_ivp_problem(
equations,
variables,
time_arg,
options,
SymbolicIvpGeneratedBackendConfig::defaults(),
)
.expect("defaults should fall back to lambdify");
assert_eq!(
prepared.selected_backend,
SelectedSymbolicIvpBackendKind::Lambdify
);
assert_eq!(prepared.problem.backend_kind, IvpBackendKind::Lambdify);
}
#[test]
fn generated_ivp_require_prebuilt_surfaces_missing_artifact() {
let (equations, variables, time_arg, options) = sample_problem();
let result = prepare_generated_symbolic_ivp_problem(
equations,
variables,
time_arg,
options,
SymbolicIvpGeneratedBackendConfig::require_prebuilt(),
);
match result {
Err(SymbolicIvpGeneratedError::CompiledAotArtifactMissing(_)) => {}
Err(other) => panic!("expected missing compiled artifact error, got {other}"),
Ok(_) => panic!("missing prebuilt artifact should be surfaced"),
}
}
#[test]
fn generated_ivp_backend_config_selects_c_and_zig_backends() {
let c_tcc = SymbolicIvpGeneratedBackendConfig::defaults().with_c_tcc();
assert_eq!(c_tcc.aot_codegen_backend, AotCodegenBackend::C);
assert_eq!(c_tcc.aot_c_compiler.as_deref(), Some("tcc"));
let c_gcc = SymbolicIvpGeneratedBackendConfig::defaults().with_c_gcc();
assert_eq!(c_gcc.aot_codegen_backend, AotCodegenBackend::C);
assert_eq!(c_gcc.aot_c_compiler.as_deref(), Some("gcc"));
let zig = SymbolicIvpGeneratedBackendConfig::defaults()
.with_c_tcc()
.with_zig();
assert_eq!(zig.aot_codegen_backend, AotCodegenBackend::Zig);
assert_eq!(zig.aot_c_compiler, None);
let rust = SymbolicIvpGeneratedBackendConfig::defaults()
.with_c_gcc()
.with_rust();
assert_eq!(rust.aot_codegen_backend, AotCodegenBackend::Rust);
assert_eq!(rust.aot_c_compiler, None);
}
#[test]
fn generated_ivp_build_if_missing_release_prefers_c_gcc() {
let config =
SymbolicIvpGeneratedBackendConfig::build_if_missing_release("target/generated-ivp");
assert_eq!(config.aot_codegen_backend, AotCodegenBackend::C);
assert_eq!(config.aot_c_compiler.as_deref(), Some("gcc"));
assert_eq!(
config.build_policy,
SymbolicIvpAotBuildPolicy::BuildIfMissing {
profile: AotBuildProfile::Release
}
);
}
#[test]
fn generated_ivp_build_if_missing_materializes_build_and_updates_resolver() {
let (equations, variables, time_arg, options) = sample_problem();
let dir = tempdir().expect("tempdir should exist");
let prepared = prepare_generated_symbolic_ivp_problem(
equations,
variables,
time_arg,
options,
SymbolicIvpGeneratedBackendConfig::defaults()
.with_build_policy(SymbolicIvpAotBuildPolicy::BuildIfMissing {
profile: AotBuildProfile::Debug,
})
.with_output_parent_dir(Some(dir.path().to_path_buf())),
)
.expect("build-if-missing should succeed");
assert_eq!(
prepared.selected_backend,
SelectedSymbolicIvpBackendKind::AotCompiled
);
assert!(prepared.build_result.is_some());
let resolver = prepared
.updated_resolver
.expect("build should update resolver");
assert_eq!(resolver.registry().len(), 1);
}
#[test]
fn generated_ivp_reuses_updated_resolver_with_linked_runtime() {
let (equations, variables, time_arg, options) = sample_problem();
let dir = tempdir().expect("tempdir should exist");
let first = prepare_generated_symbolic_ivp_problem(
equations.clone(),
variables.clone(),
time_arg.clone(),
options.clone(),
SymbolicIvpGeneratedBackendConfig::defaults()
.with_build_policy(SymbolicIvpAotBuildPolicy::BuildIfMissing {
profile: AotBuildProfile::Debug,
})
.with_output_parent_dir(Some(dir.path().to_path_buf())),
)
.expect("first build should succeed");
let resolver = first
.updated_resolver
.clone()
.expect("build should produce resolver");
let baseline = prepare_symbolic_ivp_problem(equations, variables, time_arg, options)
.expect("baseline IVP problem should prepare");
let prepared = baseline.prepare_dense_aot_problem(SymbolicIvpAotOptions::default());
let problem_key = prepared.problem_key();
register_linked_dense_backend(LinkedDenseAotBackend::new(
problem_key.clone(),
2,
(2, 2),
Arc::new(|args: &[f64], out: &mut [f64]| {
let t = args[0];
let a = args[1];
let b = args[2];
let c = args[3];
let y = args[4];
let z = args[5];
out[0] = a * t + y + b * z;
out[1] = c * y - z + b * t;
}),
Arc::new(|args: &[f64], out: &mut [f64]| {
let c = args[3];
out[0] = 1.0;
out[1] = args[2];
out[2] = c;
out[3] = -1.0;
}),
));
let second = prepare_generated_symbolic_ivp_problem(
vec![
Expr::parse_expression("a*t + y + b*z"),
Expr::parse_expression("c*y - z + b*t"),
],
vec!["y".to_string(), "z".to_string()],
"t".to_string(),
SymbolicIvpProblemOptions::new()
.with_equation_parameters(vec!["a".to_string(), "b".to_string(), "c".to_string()])
.with_equation_parameter_values(DVector::from_vec(vec![2.0, -0.5, 3.0])),
SymbolicIvpGeneratedBackendConfig::defaults().with_resolver(Some(resolver)),
)
.expect("second prepare should reuse resolver and linked runtime");
assert_eq!(
second.selected_backend,
SelectedSymbolicIvpBackendKind::AotCompiled
);
assert_eq!(second.problem.backend_kind, IvpBackendKind::Aot);
unregister_linked_dense_backend(problem_key.as_str());
}
#[test]
fn generated_ivp_transient_infra_failure_detector_matches_known_lock_and_spawn_signatures() {
assert!(is_transient_aot_infra_failure(
"tcc: error: could not write 'x.dll': Permission denied"
));
assert!(is_transient_aot_infra_failure(
"error: failed to spawn build runner target\\...\\build.zig"
));
assert!(is_transient_aot_infra_failure(
"The process cannot access the file because it is being used by another process"
));
assert!(!is_transient_aot_infra_failure(
"symbolic parse error: unknown variable q"
));
}
}