Skip to main content

wowlab_engine_domain/rotation/
compiler.rs

1// #t(file: rust_pub_api_docs) thin entry shell around the generic lowerer
2// #t(file: rust_panic_in_result_fn, rust_unwrap_in_lib) inkwell builder methods return Result; unwrap is safe for well-formed IR
3
4//! LLVM JIT shell: builds the function prototype and drives the generic lowerer.
5
6use std::sync::Once;
7
8use inkwell::{
9    AddressSpace, OptimizationLevel,
10    context::Context,
11    execution_engine::ExecutionEngine,
12    targets::{InitializationConfig, Target},
13};
14use ouroboros::self_referencing;
15use wowlab_types::sim::Rotation;
16
17use super::{
18    backend::RuntimeBackend,
19    buffer::DenseBuffer,
20    context::ContextSchema,
21    error::{Error, Result},
22    jit_backend::{Intrinsics, JitBackend, JitEntry, JitEnvironment},
23    jit_common::{RotationFn, SyncFnPtr},
24    lower,
25    resolver::SpecResolver,
26    result::EvalResult,
27};
28
29const ROTATION_FN_NAME: &str = "rotation";
30
31const ROTATION_MODULE_NAME: &str = "rotation";
32
33#[self_referencing]
34struct LlvmBundle {
35    context: Context,
36    #[borrows(context)]
37    #[covariant]
38    execution_engine: ExecutionEngine<'this>,
39}
40
41pub struct CompiledRotation {
42    func_ptr: SyncFnPtr,
43    schema: ContextSchema,
44    pub(crate) rotation: Rotation,
45    pub(crate) resolver: SpecResolver,
46    // Field-drop order matters: bundle drops last so JIT code outlives in-flight calls.
47    _bundle: LlvmBundle,
48}
49
50impl CompiledRotation {
51    /// Parses, validates, and JIT-compiles a rotation.
52    ///
53    /// # Errors
54    /// Returns an error when parsing, lowering, or native compilation fails.
55    pub fn compile_json(json: &str, resolver: &SpecResolver) -> Result<Self> {
56        let rotation = super::ast::parse_and_validate(json)?;
57
58        Self::compile_resolved(&rotation, resolver)
59    }
60
61    /// JIT-compiles an already parsed rotation.
62    ///
63    /// # Errors
64    /// Returns an error when lowering or native compilation fails.
65    pub fn compile(rotation: &Rotation, resolver: &SpecResolver) -> Result<Self> {
66        Self::compile_resolved(rotation, resolver)
67    }
68
69    fn compile_resolved(rotation: &Rotation, resolver: &SpecResolver) -> Result<Self> {
70        compile_inner(rotation, resolver)
71    }
72}
73
74impl std::fmt::Debug for CompiledRotation {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        f.debug_struct("CompiledRotation").finish_non_exhaustive()
77    }
78}
79
80// SAFETY: bundle is owned by one CompiledRotation; evaluate takes &mut self, so the
81// non-atomic Rc in ExecutionEngine cannot be accessed concurrently.
82unsafe impl Send for CompiledRotation {}
83
84impl RuntimeBackend for CompiledRotation {
85    fn evaluate(&mut self, buffer: &mut DenseBuffer, now_secs: f64) -> EvalResult {
86        // SAFETY: `_bundle` owns the ExecutionEngine that keeps `func_ptr` valid.
87        let packed = unsafe { (self.func_ptr.0)(buffer.as_mut_ptr(), now_secs) };
88        let (kind, spell_id, payload) = wowlab_buffer_contract::decode_eval_result(packed);
89        let empower_rank = if matches!(
90            kind,
91            super::result::KIND_CAST | super::result::KIND_USE_ITEM
92        ) {
93            u8::try_from(payload).unwrap_or(0)
94        } else {
95            0
96        };
97
98        EvalResult {
99            kind,
100            empower_rank,
101            spell_id,
102            wait_time: if matches!(kind, super::result::KIND_WAIT | super::result::KIND_POOL) {
103                f32::from_bits(payload)
104            } else {
105                0.0
106            },
107        }
108    }
109
110    fn schema(&self) -> &ContextSchema {
111        &self.schema
112    }
113}
114
115static INIT_NATIVE: Once = Once::new();
116
117fn ensure_native_target() {
118    INIT_NATIVE.call_once(|| {
119        Target::initialize_native(&InitializationConfig::default())
120            .expect("failed to initialize native LLVM target");
121        ExecutionEngine::link_in_mc_jit();
122    });
123}
124
125fn compile_inner(rotation: &Rotation, resolver: &SpecResolver) -> Result<CompiledRotation> {
126    ensure_native_target();
127
128    let (schema, table) = lower::prepare(rotation, resolver)?;
129
130    let mut captured_fn: Option<RotationFn> = None;
131    let bundle = LlvmBundle::try_new::<Error>(Context::create(), |context| {
132        let module = context.create_module(ROTATION_MODULE_NAME);
133        let builder = context.create_builder();
134
135        let i64_type = context.i64_type();
136        let f64_type = context.f64_type();
137        let ptr_type = context.ptr_type(AddressSpace::default());
138
139        let fn_type = i64_type.fn_type(&[ptr_type.into(), f64_type.into()], false);
140        let function = module.add_function(ROTATION_FN_NAME, fn_type, None);
141
142        let intrinsics = Intrinsics::declare(context, &module);
143
144        let entry_block = context.append_basic_block(function, "entry");
145
146        builder.position_at_end(entry_block);
147
148        let buf_param = function.get_nth_param(0).unwrap().into_pointer_value();
149        let now_param = function.get_nth_param(1).unwrap().into_float_value();
150
151        let environment = JitEnvironment {
152            context,
153            builder: &builder,
154        };
155        let entry = JitEntry {
156            function,
157            buffer: buf_param,
158            now: now_param,
159        };
160        let mut backend = JitBackend::new(&environment, &entry, intrinsics);
161
162        lower::lower_rotation(&mut backend, rotation, &schema, resolver, &table);
163
164        if let Err(msg) = module.verify() {
165            let ir = module.print_to_string().to_string();
166
167            return Err(Error::compilation(format!(
168                "LLVM module verification failed: {}\nIR:\n{}",
169                msg.to_string(),
170                ir,
171            )));
172        }
173
174        let ee = module
175            .create_jit_execution_engine(OptimizationLevel::Aggressive)
176            .map_err(|error| {
177                Error::compilation(format!("failed to create LLVM JIT engine: {error}"))
178            })?;
179
180        // SAFETY: signature matches RotationFn; EE moves into the bundle below.
181        let jit_fn = unsafe { ee.get_function::<RotationFn>(ROTATION_FN_NAME).unwrap() };
182        // SAFETY: same as above; bundle keeps the code alive past this scope.
183
184        captured_fn = Some(unsafe { jit_fn.as_raw() });
185
186        Ok(ee)
187    })?;
188
189    let raw_fn =
190        captured_fn.ok_or_else(|| Error::compilation("JIT function pointer not captured"))?;
191
192    Ok(CompiledRotation {
193        func_ptr: SyncFnPtr(raw_fn),
194        schema,
195        rotation: rotation.clone(),
196        resolver: resolver.clone(),
197        _bundle: bundle,
198    })
199}