Skip to main content

wowlab_engine_domain/rotation/
jit_backend.rs

1// #t(file: rust_unwrap_in_lib) inkwell builder methods return `Result` for IR-construction errors that cannot occur for well-formed input; upstream `compile_inner` runs `module.verify()` after lowering.
2
3//! JIT side of the [`RotationBackend`] lowering trait.
4
5use inkwell::{
6    FloatPredicate, IntPredicate,
7    builder::Builder,
8    context::Context,
9    types::{FloatType, IntType},
10    values::{FloatValue, FunctionValue, IntValue, PointerValue},
11};
12use wowlab_types::numeric::EPSILON;
13
14use super::{
15    backend::RotationBackend,
16    result::{KIND_CAST, KIND_NONE, KIND_POOL, KIND_USE_ITEM, KIND_WAIT},
17};
18
19const I64_EXCLUSIVE_UPPER_BOUND_AS_F64: f64 = 9_223_372_036_854_775_808.0;
20
21pub(crate) struct Intrinsics<'ctx> {
22    pub floor: FunctionValue<'ctx>,
23    pub ceil: FunctionValue<'ctx>,
24    pub fabs: FunctionValue<'ctx>,
25    pub minnum: FunctionValue<'ctx>,
26    pub maxnum: FunctionValue<'ctx>,
27}
28
29impl<'ctx> Intrinsics<'ctx> {
30    pub(crate) fn declare(ctx: &'ctx Context, module: &inkwell::module::Module<'ctx>) -> Self {
31        let f64_type = ctx.f64_type();
32        let unary = f64_type.fn_type(&[f64_type.into()], false);
33        let binary = f64_type.fn_type(&[f64_type.into(), f64_type.into()], false);
34
35        Self {
36            floor: module.add_function("llvm.floor.f64", unary, None),
37            ceil: module.add_function("llvm.ceil.f64", unary, None),
38            fabs: module.add_function("llvm.fabs.f64", unary, None),
39            minnum: module.add_function("llvm.minnum.f64", binary, None),
40            maxnum: module.add_function("llvm.maxnum.f64", binary, None),
41        }
42    }
43}
44
45pub(crate) struct JitBackend<'ctx, 'b> {
46    pub(crate) ctx: &'ctx Context,
47    pub(crate) builder: &'b Builder<'ctx>,
48    pub(crate) function: FunctionValue<'ctx>,
49    pub(crate) buf_param: PointerValue<'ctx>,
50    pub(crate) now_param: FloatValue<'ctx>,
51    pub(crate) i8_type: IntType<'ctx>,
52    pub(crate) i32_type: IntType<'ctx>,
53    pub(crate) i64_type: IntType<'ctx>,
54    pub(crate) f64_type: FloatType<'ctx>,
55    pub(crate) bool_type: IntType<'ctx>,
56    pub(crate) intrinsics: Intrinsics<'ctx>,
57}
58
59pub(crate) struct JitEnvironment<'ctx, 'b> {
60    pub(crate) context: &'ctx Context,
61    pub(crate) builder: &'b Builder<'ctx>,
62}
63
64pub(crate) struct JitEntry<'ctx> {
65    pub(crate) function: FunctionValue<'ctx>,
66    pub(crate) buffer: PointerValue<'ctx>,
67    pub(crate) now: FloatValue<'ctx>,
68}
69
70impl<'ctx, 'b> JitBackend<'ctx, 'b> {
71    pub(crate) fn new(
72        environment: &JitEnvironment<'ctx, 'b>,
73        entry: &JitEntry<'ctx>,
74        intrinsics: Intrinsics<'ctx>,
75    ) -> Self {
76        let JitEnvironment {
77            context: ctx,
78            builder,
79        } = environment;
80        let JitEntry {
81            function,
82            buffer: buf_param,
83            now: now_param,
84        } = entry;
85
86        Self {
87            ctx,
88            builder,
89            function: *function,
90            buf_param: *buf_param,
91            now_param: *now_param,
92            i8_type: ctx.i8_type(),
93            i32_type: ctx.i32_type(),
94            i64_type: ctx.i64_type(),
95            f64_type: ctx.f64_type(),
96            bool_type: ctx.bool_type(),
97            intrinsics,
98        }
99    }
100
101    pub(crate) fn pack_result(&self, kind: u8, action_id: u32, payload: u32) -> IntValue<'ctx> {
102        let packed = wowlab_buffer_contract::pack_eval_result(kind, action_id, payload);
103
104        self.i64_type.const_int(packed, false)
105    }
106
107    #[inline]
108    fn gep(&self, offset: usize) -> PointerValue<'ctx> {
109        let offset_val = self.i64_type.const_int(offset as u64, false);
110        // SAFETY: offsets are schema-validated; GEP stays within the buffer.
111
112        unsafe {
113            self.builder
114                .build_in_bounds_gep(self.i8_type, self.buf_param, &[offset_val], "ptr")
115                .unwrap()
116        }
117    }
118
119    fn cond_return(&mut self, cond: IntValue<'ctx>, ret: IntValue<'ctx>, label: &str) {
120        let return_block = self
121            .ctx
122            .append_basic_block(self.function, &format!("ret_{label}"));
123        let cont_block = self
124            .ctx
125            .append_basic_block(self.function, &format!("after_{label}"));
126
127        self.builder
128            .build_conditional_branch(cond, return_block, cont_block)
129            .unwrap();
130        self.builder.position_at_end(return_block);
131        self.builder.build_return(Some(&ret)).unwrap();
132        self.builder.position_at_end(cont_block);
133    }
134}
135
136impl<'ctx> RotationBackend for JitBackend<'ctx, '_> {
137    type Bool = IntValue<'ctx>;
138    type Int = IntValue<'ctx>;
139    type Float = FloatValue<'ctx>;
140    type AndChain = IntValue<'ctx>;
141    type OrChain = IntValue<'ctx>;
142    #[cfg(feature = "rotation-optimizations")]
143    type CastGuard = IntValue<'ctx>;
144
145    fn const_bool(&mut self, v: bool) -> Self::Bool {
146        self.bool_type.const_int(u64::from(v), false)
147    }
148    fn const_i32(&mut self, v: i32) -> Self::Int {
149        self.i64_type
150            .const_int(u64::from_ne_bytes(i64::from(v).to_ne_bytes()), true)
151    }
152    fn const_i64(&mut self, v: i64) -> Self::Int {
153        self.i64_type
154            .const_int(u64::from_ne_bytes(v.to_ne_bytes()), true)
155    }
156    fn const_f64(&mut self, v: f64) -> Self::Float {
157        self.f64_type.const_float(v)
158    }
159
160    fn load_bool(&mut self, offset: usize) -> Self::Bool {
161        let raw = self
162            .builder
163            .build_load(self.i32_type, self.gep(offset), "bool_raw")
164            .unwrap()
165            .into_int_value();
166        let zero = self.i32_type.const_int(0, false);
167
168        self.builder
169            .build_int_compare(IntPredicate::NE, raw, zero, "bool")
170            .unwrap()
171    }
172    fn load_i32(&mut self, offset: usize) -> Self::Int {
173        let raw = self
174            .builder
175            .build_load(self.i32_type, self.gep(offset), "i32_raw")
176            .unwrap()
177            .into_int_value();
178
179        self.builder
180            .build_int_s_extend(raw, self.i64_type, "i32_to_i64")
181            .unwrap()
182    }
183    fn load_f64(&mut self, offset: usize) -> Self::Float {
184        self.builder
185            .build_load(self.f64_type, self.gep(offset), "f64")
186            .unwrap()
187            .into_float_value()
188    }
189
190    fn store_bool(&mut self, offset: usize, v: Self::Bool) {
191        let widened = self
192            .builder
193            .build_int_z_extend(v, self.i32_type, "bool_to_i32")
194            .unwrap();
195
196        self.builder.build_store(self.gep(offset), widened).unwrap();
197    }
198    fn store_i32(&mut self, offset: usize, v: Self::Int) {
199        let truncated = self
200            .builder
201            .build_int_truncate(v, self.i32_type, "i64_to_i32")
202            .unwrap();
203
204        self.builder
205            .build_store(self.gep(offset), truncated)
206            .unwrap();
207    }
208    fn store_f64(&mut self, offset: usize, v: Self::Float) {
209        self.builder.build_store(self.gep(offset), v).unwrap();
210    }
211
212    fn now(&mut self) -> Self::Float {
213        self.now_param
214    }
215
216    fn add_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
217        self.builder.build_float_add(a, b, "fadd").unwrap()
218    }
219    fn sub_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
220        self.builder.build_float_sub(a, b, "fsub").unwrap()
221    }
222    fn mul_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
223        self.builder.build_float_mul(a, b, "fmul").unwrap()
224    }
225    fn safe_div_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
226        let zero = self.f64_type.const_float(0.0);
227        let is_zero = self
228            .builder
229            .build_float_compare(FloatPredicate::OEQ, b, zero, "div_zero")
230            .unwrap();
231        let div = self.builder.build_float_div(a, b, "fdiv").unwrap();
232
233        self.builder
234            .build_select(is_zero, zero, div, "safe_div")
235            .unwrap()
236            .into_float_value()
237    }
238    fn true_mod_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
239        let zero = self.f64_type.const_float(0.0);
240        let is_zero = self
241            .builder
242            .build_float_compare(FloatPredicate::OEQ, b, zero, "mod_zero")
243            .unwrap();
244        let div = self.builder.build_float_div(a, b, "mod_div").unwrap();
245        let floored = self.floor_f(div);
246        let prod = self
247            .builder
248            .build_float_mul(b, floored, "mod_prod")
249            .unwrap();
250        let std_mod = self.builder.build_float_sub(a, prod, "mod_std").unwrap();
251        let with_b = self
252            .builder
253            .build_float_add(std_mod, b, "mod_with_b")
254            .unwrap();
255        let div2 = self.builder.build_float_div(with_b, b, "mod_div2").unwrap();
256        let floored2 = self.floor_f(div2);
257        let prod2 = self
258            .builder
259            .build_float_mul(b, floored2, "mod_prod2")
260            .unwrap();
261        let result = self.builder.build_float_sub(with_b, prod2, "mod").unwrap();
262
263        self.builder
264            .build_select(is_zero, zero, result, "true_mod")
265            .unwrap()
266            .into_float_value()
267    }
268    fn min_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
269        self.builder
270            .build_call(self.intrinsics.minnum, &[a.into(), b.into()], "fmin")
271            .unwrap()
272            .try_as_basic_value()
273            .left()
274            .unwrap()
275            .into_float_value()
276    }
277    fn max_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
278        self.builder
279            .build_call(self.intrinsics.maxnum, &[a.into(), b.into()], "fmax")
280            .unwrap()
281            .try_as_basic_value()
282            .left()
283            .unwrap()
284            .into_float_value()
285    }
286    fn floor_f(&mut self, v: Self::Float) -> Self::Float {
287        self.builder
288            .build_call(self.intrinsics.floor, &[v.into()], "floor")
289            .unwrap()
290            .try_as_basic_value()
291            .left()
292            .unwrap()
293            .into_float_value()
294    }
295    fn ceil_f(&mut self, v: Self::Float) -> Self::Float {
296        self.builder
297            .build_call(self.intrinsics.ceil, &[v.into()], "ceil")
298            .unwrap()
299            .try_as_basic_value()
300            .left()
301            .unwrap()
302            .into_float_value()
303    }
304    fn abs_f(&mut self, v: Self::Float) -> Self::Float {
305        self.builder
306            .build_call(self.intrinsics.fabs, &[v.into()], "fabs")
307            .unwrap()
308            .try_as_basic_value()
309            .left()
310            .unwrap()
311            .into_float_value()
312    }
313
314    fn cmp_gt_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
315        self.builder
316            .build_float_compare(FloatPredicate::OGT, a, b, "fgt")
317            .unwrap()
318    }
319    fn cmp_gte_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
320        self.builder
321            .build_float_compare(FloatPredicate::OGE, a, b, "fge")
322            .unwrap()
323    }
324    fn cmp_lt_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
325        self.builder
326            .build_float_compare(FloatPredicate::OLT, a, b, "flt")
327            .unwrap()
328    }
329    fn cmp_lte_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
330        self.builder
331            .build_float_compare(FloatPredicate::OLE, a, b, "fle")
332            .unwrap()
333    }
334    fn cmp_eq_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
335        let diff = self.builder.build_float_sub(a, b, "eps_diff").unwrap();
336        let abs = self.abs_f(diff);
337        let eps = self.f64_type.const_float(EPSILON);
338
339        self.builder
340            .build_float_compare(FloatPredicate::OLT, abs, eps, "feq")
341            .unwrap()
342    }
343    fn cmp_ne_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
344        let diff = self.builder.build_float_sub(a, b, "eps_diff").unwrap();
345        let abs = self.abs_f(diff);
346        let eps = self.f64_type.const_float(EPSILON);
347
348        self.builder
349            .build_float_compare(FloatPredicate::OGE, abs, eps, "fne")
350            .unwrap()
351    }
352    fn cmp_eq_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
353        self.builder
354            .build_int_compare(IntPredicate::EQ, a, b, "ieq")
355            .unwrap()
356    }
357    fn cmp_ne_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
358        self.builder
359            .build_int_compare(IntPredicate::NE, a, b, "ine")
360            .unwrap()
361    }
362    fn cmp_gt_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
363        self.builder
364            .build_int_compare(IntPredicate::SGT, a, b, "igt")
365            .unwrap()
366    }
367    fn cmp_gte_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
368        self.builder
369            .build_int_compare(IntPredicate::SGE, a, b, "ige")
370            .unwrap()
371    }
372    fn cmp_lt_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
373        self.builder
374            .build_int_compare(IntPredicate::SLT, a, b, "ilt")
375            .unwrap()
376    }
377    fn cmp_lte_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
378        self.builder
379            .build_int_compare(IntPredicate::SLE, a, b, "ile")
380            .unwrap()
381    }
382
383    fn and_b(&mut self, a: Self::Bool, b: Self::Bool) -> Self::Bool {
384        self.builder.build_and(a, b, "and").unwrap()
385    }
386    fn or_b(&mut self, a: Self::Bool, b: Self::Bool) -> Self::Bool {
387        self.builder.build_or(a, b, "or").unwrap()
388    }
389    fn not_b(&mut self, a: Self::Bool) -> Self::Bool {
390        let one = self.bool_type.const_int(1, false);
391
392        self.builder.build_xor(a, one, "not").unwrap()
393    }
394
395    fn begin_and(&mut self, first: Self::Bool) -> Self::AndChain {
396        first
397    }
398
399    fn push_and(&mut self, chain: &mut Self::AndChain, value: Self::Bool) {
400        *chain = self.and_b(*chain, value);
401    }
402
403    fn finish_and(&mut self, chain: Self::AndChain) -> Self::Bool {
404        chain
405    }
406
407    fn begin_or(&mut self, first: Self::Bool) -> Self::OrChain {
408        first
409    }
410
411    fn push_or(&mut self, chain: &mut Self::OrChain, value: Self::Bool) {
412        *chain = self.or_b(*chain, value);
413    }
414
415    fn finish_or(&mut self, chain: Self::OrChain) -> Self::Bool {
416        chain
417    }
418
419    #[cfg(feature = "rotation-optimizations")]
420    fn begin_cast_guard(&mut self, first: Self::Bool) -> Self::CastGuard {
421        first
422    }
423
424    #[cfg(feature = "rotation-optimizations")]
425    fn push_cast_guard(&mut self, guard: &mut Self::CastGuard, value: Self::Bool) {
426        *guard = self.and_b(*guard, value);
427    }
428
429    #[cfg(feature = "rotation-optimizations")]
430    fn finish_cast_guard(
431        &mut self,
432        guard: Self::CastGuard,
433        spell_id: u32,
434        empower_rank: u8,
435    ) -> Self::Bool {
436        self.return_cast_if(guard, spell_id, empower_rank);
437
438        guard
439    }
440
441    fn bool_to_float(&mut self, v: Self::Bool) -> Self::Float {
442        let one = self.f64_type.const_float(1.0);
443        let zero = self.f64_type.const_float(0.0);
444
445        self.builder
446            .build_select(v, one, zero, "b2f")
447            .unwrap()
448            .into_float_value()
449    }
450    fn bool_to_int(&mut self, v: Self::Bool) -> Self::Int {
451        self.builder
452            .build_int_z_extend(v, self.i64_type, "b2i")
453            .unwrap()
454    }
455    fn int_to_float(&mut self, v: Self::Int) -> Self::Float {
456        self.builder
457            .build_signed_int_to_float(v, self.f64_type, "i2f")
458            .unwrap()
459    }
460    fn int_to_bool(&mut self, v: Self::Int) -> Self::Bool {
461        let zero = self.i64_type.const_int(0, false);
462
463        self.builder
464            .build_int_compare(IntPredicate::NE, v, zero, "i2b")
465            .unwrap()
466    }
467    fn float_to_bool(&mut self, v: Self::Float) -> Self::Bool {
468        let zero = self.f64_type.const_float(0.0);
469        // Exact `v != 0.0` (ONE), not epsilon-aware equality.
470
471        self.builder
472            .build_float_compare(FloatPredicate::ONE, v, zero, "f2b")
473            .unwrap()
474    }
475    fn float_to_int(&mut self, v: Self::Float) -> Self::Int {
476        let zero_f = self.f64_type.const_zero();
477        let zero_i = self.i64_type.const_zero();
478        let upper_bound = self.f64_type.const_float(I64_EXCLUSIVE_UPPER_BOUND_AS_F64);
479        let lower_bound = self.f64_type.const_float(-I64_EXCLUSIVE_UPPER_BOUND_AS_F64);
480        let is_nan = self
481            .builder
482            .build_float_compare(FloatPredicate::UNO, v, v, "f2i_nan")
483            .unwrap();
484        let overflows_high = self
485            .builder
486            .build_float_compare(FloatPredicate::OGE, v, upper_bound, "f2i_high")
487            .unwrap();
488        let overflows_low = self
489            .builder
490            .build_float_compare(FloatPredicate::OLT, v, lower_bound, "f2i_low")
491            .unwrap();
492        let invalid = self
493            .builder
494            .build_or(is_nan, overflows_high, "f2i_nan_or_high")
495            .unwrap();
496        let invalid = self
497            .builder
498            .build_or(invalid, overflows_low, "f2i_invalid")
499            .unwrap();
500        let safe_input = self
501            .builder
502            .build_select(invalid, zero_f, v, "f2i_safe_input")
503            .unwrap()
504            .into_float_value();
505        let converted = self
506            .builder
507            .build_float_to_signed_int(safe_input, self.i64_type, "f2i")
508            .unwrap();
509        let with_low = self
510            .builder
511            .build_select(
512                overflows_low,
513                self.i64_type
514                    .const_int(u64::from_ne_bytes(i64::MIN.to_ne_bytes()), false),
515                converted,
516                "f2i_with_low",
517            )
518            .unwrap()
519            .into_int_value();
520        let with_high = self
521            .builder
522            .build_select(
523                overflows_high,
524                self.i64_type
525                    .const_int(u64::from_ne_bytes(i64::MAX.to_ne_bytes()), false),
526                with_low,
527                "f2i_with_high",
528            )
529            .unwrap()
530            .into_int_value();
531
532        self.builder
533            .build_select(is_nan, zero_i, with_high, "f2i_saturated")
534            .unwrap()
535            .into_int_value()
536    }
537
538    fn select_f(&mut self, cond: Self::Bool, t: Self::Float, f: Self::Float) -> Self::Float {
539        self.builder
540            .build_select(cond, t, f, "sel_f")
541            .unwrap()
542            .into_float_value()
543    }
544    fn select_i(&mut self, cond: Self::Bool, t: Self::Int, f: Self::Int) -> Self::Int {
545        self.builder
546            .build_select(cond, t, f, "sel_i")
547            .unwrap()
548            .into_int_value()
549    }
550    fn select_b(&mut self, cond: Self::Bool, t: Self::Bool, f: Self::Bool) -> Self::Bool {
551        self.builder
552            .build_select(cond, t, f, "sel_b")
553            .unwrap()
554            .into_int_value()
555    }
556
557    fn return_cast_if(&mut self, cond: Self::Bool, spell_id: u32, empower_rank: u8) {
558        let ret = self.pack_result(KIND_CAST, spell_id, u32::from(empower_rank));
559
560        self.cond_return(cond, ret, "cast");
561    }
562    fn return_wait_if(&mut self, cond: Self::Bool, seconds: f32) {
563        let ret = self.pack_result(KIND_WAIT, 0, seconds.to_bits());
564
565        self.cond_return(cond, ret, "wait");
566    }
567    fn return_pool_if(&mut self, cond: Self::Bool, target: f32) {
568        let ret = self.pack_result(KIND_POOL, 0, target.to_bits());
569
570        self.cond_return(cond, ret, "pool");
571    }
572    fn return_use_item_if(&mut self, cond: Self::Bool, gear_slot: u8, empower_rank: u8) {
573        let ret = self.pack_result(KIND_USE_ITEM, u32::from(gear_slot), u32::from(empower_rank));
574
575        self.cond_return(cond, ret, "use_item");
576    }
577    fn return_none(&mut self) {
578        let ret = self.pack_result(KIND_NONE, 0, 0);
579
580        self.builder.build_return(Some(&ret)).unwrap();
581    }
582}