wowlab_engine_domain/rotation/
compiler.rs1use 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 _bundle: LlvmBundle,
48}
49
50impl CompiledRotation {
51 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 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
80unsafe impl Send for CompiledRotation {}
83
84impl RuntimeBackend for CompiledRotation {
85 fn evaluate(&mut self, buffer: &mut DenseBuffer, now_secs: f64) -> EvalResult {
86 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 let jit_fn = unsafe { ee.get_function::<RotationFn>(ROTATION_FN_NAME).unwrap() };
182 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}