Skip to main content

tidepool_codegen/emit/
primop.rs

1use super::*;
2use crate::emit::{SsaVal, EmitError};
3use tidepool_repr::PrimOpKind;
4use cranelift_codegen::ir::{types, InstBuilder, MemFlags, Value, condcodes::IntCC, condcodes::FloatCC};
5use cranelift_frontend::FunctionBuilder;
6
7/// Emit a primitive operation. Unboxes HeapPtr args, performs the op, returns Raw.
8pub fn emit_primop(
9    builder: &mut FunctionBuilder,
10    op: &PrimOpKind,
11    args: &[SsaVal],
12) -> Result<SsaVal, EmitError> {
13    match op {
14        // Int arithmetic (binary)
15        PrimOpKind::IntAdd => {
16            check_arity(op, 2, args.len())?;
17            let a = unbox_int(builder, args[0]);
18            let b = unbox_int(builder, args[1]);
19            Ok(SsaVal::Raw(builder.ins().iadd(a, b), LIT_TAG_INT))
20        }
21        PrimOpKind::IntSub => {
22            check_arity(op, 2, args.len())?;
23            let a = unbox_int(builder, args[0]);
24            let b = unbox_int(builder, args[1]);
25            Ok(SsaVal::Raw(builder.ins().isub(a, b), LIT_TAG_INT))
26        }
27        PrimOpKind::IntMul => {
28            check_arity(op, 2, args.len())?;
29            let a = unbox_int(builder, args[0]);
30            let b = unbox_int(builder, args[1]);
31            Ok(SsaVal::Raw(builder.ins().imul(a, b), LIT_TAG_INT))
32        }
33        PrimOpKind::IntNegate => {
34            check_arity(op, 1, args.len())?;
35            let a = unbox_int(builder, args[0]);
36            Ok(SsaVal::Raw(builder.ins().ineg(a), LIT_TAG_INT))
37        }
38
39        // Int comparison → returns i64 (0=False, 1=True)
40        PrimOpKind::IntEq => emit_int_compare(builder, op, IntCC::Equal, args, LIT_TAG_INT),
41        PrimOpKind::IntNe => emit_int_compare(builder, op, IntCC::NotEqual, args, LIT_TAG_INT),
42        PrimOpKind::IntLt => emit_int_compare(builder, op, IntCC::SignedLessThan, args, LIT_TAG_INT),
43        PrimOpKind::IntLe => emit_int_compare(builder, op, IntCC::SignedLessThanOrEqual, args, LIT_TAG_INT),
44        PrimOpKind::IntGt => emit_int_compare(builder, op, IntCC::SignedGreaterThan, args, LIT_TAG_INT),
45        PrimOpKind::IntGe => emit_int_compare(builder, op, IntCC::SignedGreaterThanOrEqual, args, LIT_TAG_INT),
46
47        // Word arithmetic
48        PrimOpKind::WordAdd => {
49            check_arity(op, 2, args.len())?;
50            let a = unbox_int(builder, args[0]);
51            let b = unbox_int(builder, args[1]);
52            Ok(SsaVal::Raw(builder.ins().iadd(a, b), LIT_TAG_WORD))
53        }
54        PrimOpKind::WordSub => {
55            check_arity(op, 2, args.len())?;
56            let a = unbox_int(builder, args[0]);
57            let b = unbox_int(builder, args[1]);
58            Ok(SsaVal::Raw(builder.ins().isub(a, b), LIT_TAG_WORD))
59        }
60        PrimOpKind::WordMul => {
61            check_arity(op, 2, args.len())?;
62            let a = unbox_int(builder, args[0]);
63            let b = unbox_int(builder, args[1]);
64            Ok(SsaVal::Raw(builder.ins().imul(a, b), LIT_TAG_WORD))
65        }
66
67        // Word comparison (unsigned)
68        PrimOpKind::WordEq => emit_int_compare(builder, op, IntCC::Equal, args, LIT_TAG_INT),
69        PrimOpKind::WordNe => emit_int_compare(builder, op, IntCC::NotEqual, args, LIT_TAG_INT),
70        PrimOpKind::WordLt => emit_int_compare(builder, op, IntCC::UnsignedLessThan, args, LIT_TAG_INT),
71        PrimOpKind::WordLe => emit_int_compare(builder, op, IntCC::UnsignedLessThanOrEqual, args, LIT_TAG_INT),
72        PrimOpKind::WordGt => emit_int_compare(builder, op, IntCC::UnsignedGreaterThan, args, LIT_TAG_INT),
73        PrimOpKind::WordGe => emit_int_compare(builder, op, IntCC::UnsignedGreaterThanOrEqual, args, LIT_TAG_INT),
74
75        // Double arithmetic (unbox_double → f64, fadd/fsub/fmul/fdiv)
76        PrimOpKind::DoubleAdd => {
77            check_arity(op, 2, args.len())?;
78            let a = unbox_double(builder, args[0]);
79            let b = unbox_double(builder, args[1]);
80            Ok(SsaVal::Raw(builder.ins().fadd(a, b), LIT_TAG_DOUBLE))
81        }
82        PrimOpKind::DoubleSub => {
83            check_arity(op, 2, args.len())?;
84            let a = unbox_double(builder, args[0]);
85            let b = unbox_double(builder, args[1]);
86            Ok(SsaVal::Raw(builder.ins().fsub(a, b), LIT_TAG_DOUBLE))
87        }
88        PrimOpKind::DoubleMul => {
89            check_arity(op, 2, args.len())?;
90            let a = unbox_double(builder, args[0]);
91            let b = unbox_double(builder, args[1]);
92            Ok(SsaVal::Raw(builder.ins().fmul(a, b), LIT_TAG_DOUBLE))
93        }
94        PrimOpKind::DoubleDiv => {
95            check_arity(op, 2, args.len())?;
96            let a = unbox_double(builder, args[0]);
97            let b = unbox_double(builder, args[1]);
98            Ok(SsaVal::Raw(builder.ins().fdiv(a, b), LIT_TAG_DOUBLE))
99        }
100
101        // Double comparison → returns i64 (0 or 1)
102        PrimOpKind::DoubleEq => emit_float_compare(builder, op, FloatCC::Equal, args, LIT_TAG_INT),
103        PrimOpKind::DoubleNe => emit_float_compare(builder, op, FloatCC::NotEqual, args, LIT_TAG_INT),
104        PrimOpKind::DoubleLt => emit_float_compare(builder, op, FloatCC::LessThan, args, LIT_TAG_INT),
105        PrimOpKind::DoubleLe => emit_float_compare(builder, op, FloatCC::LessThanOrEqual, args, LIT_TAG_INT),
106        PrimOpKind::DoubleGt => emit_float_compare(builder, op, FloatCC::GreaterThan, args, LIT_TAG_INT),
107        PrimOpKind::DoubleGe => emit_float_compare(builder, op, FloatCC::GreaterThanOrEqual, args, LIT_TAG_INT),
108
109        // Char comparison → unbox_int (char stored as i64), use IntCC
110        PrimOpKind::CharEq => emit_int_compare(builder, op, IntCC::Equal, args, LIT_TAG_INT),
111        PrimOpKind::CharNe => emit_int_compare(builder, op, IntCC::NotEqual, args, LIT_TAG_INT),
112        PrimOpKind::CharLt => emit_int_compare(builder, op, IntCC::UnsignedLessThan, args, LIT_TAG_INT),
113        PrimOpKind::CharLe => emit_int_compare(builder, op, IntCC::UnsignedLessThanOrEqual, args, LIT_TAG_INT),
114        PrimOpKind::CharGt => emit_int_compare(builder, op, IntCC::UnsignedGreaterThan, args, LIT_TAG_INT),
115        PrimOpKind::CharGe => emit_int_compare(builder, op, IntCC::UnsignedGreaterThanOrEqual, args, LIT_TAG_INT),
116
117        // Special ops
118        PrimOpKind::DataToTag => {
119            check_arity(op, 1, args.len())?;
120            // Load con_tag from HeapObject at CON_TAG_OFFSET
121            let obj = args[0].value(); // HeapPtr
122            let tag = builder.ins().load(types::I64, MemFlags::trusted(), obj, CON_TAG_OFFSET);
123            Ok(SsaVal::Raw(tag, LIT_TAG_INT))
124        }
125        PrimOpKind::IntQuot => {
126            check_arity(op, 2, args.len())?;
127            let a = unbox_int(builder, args[0]);
128            let b = unbox_int(builder, args[1]);
129            Ok(SsaVal::Raw(builder.ins().sdiv(a, b), LIT_TAG_INT))
130        }
131        PrimOpKind::IntRem => {
132            check_arity(op, 2, args.len())?;
133            let a = unbox_int(builder, args[0]);
134            let b = unbox_int(builder, args[1]);
135            Ok(SsaVal::Raw(builder.ins().srem(a, b), LIT_TAG_INT))
136        }
137        PrimOpKind::Chr => {
138            check_arity(op, 1, args.len())?;
139            let v = unbox_int(builder, args[0]);
140            Ok(SsaVal::Raw(v, LIT_TAG_CHAR))
141        }
142        PrimOpKind::Ord => {
143            check_arity(op, 1, args.len())?;
144            let v = unbox_int(builder, args[0]);
145            Ok(SsaVal::Raw(v, LIT_TAG_INT))
146        }
147        PrimOpKind::TagToEnum | PrimOpKind::IndexArray | PrimOpKind::SeqOp => {
148            Err(EmitError::NotYetImplemented(format!("{:?}", op)))
149        }
150    }
151}
152
153fn check_arity(op: &PrimOpKind, expected: usize, got: usize) -> Result<(), EmitError> {
154    if expected != got {
155        Err(EmitError::InvalidArity(*op, expected, got))
156    } else {
157        Ok(())
158    }
159}
160
161fn emit_int_compare(
162    builder: &mut FunctionBuilder,
163    op: &PrimOpKind,
164    cc: IntCC,
165    args: &[SsaVal],
166    tag: i64,
167) -> Result<SsaVal, EmitError> {
168    check_arity(op, 2, args.len())?;
169    let a = unbox_int(builder, args[0]);
170    let b = unbox_int(builder, args[1]);
171    let cmp = builder.ins().icmp(cc, a, b);
172    Ok(SsaVal::Raw(builder.ins().uextend(types::I64, cmp), tag))
173}
174
175fn emit_float_compare(
176    builder: &mut FunctionBuilder,
177    op: &PrimOpKind,
178    cc: FloatCC,
179    args: &[SsaVal],
180    tag: i64,
181) -> Result<SsaVal, EmitError> {
182    check_arity(op, 2, args.len())?;
183    let a = unbox_double(builder, args[0]);
184    let b = unbox_double(builder, args[1]);
185    let cmp = builder.ins().fcmp(cc, a, b);
186    Ok(SsaVal::Raw(builder.ins().uextend(types::I64, cmp), tag))
187}
188
189pub fn unbox_int(builder: &mut FunctionBuilder, val: SsaVal) -> Value {
190    match val {
191        SsaVal::Raw(v, _) => v,
192        SsaVal::HeapPtr(v) => builder.ins().load(types::I64, MemFlags::trusted(), v, LIT_VALUE_OFFSET),
193    }
194}
195
196pub fn unbox_double(builder: &mut FunctionBuilder, val: SsaVal) -> Value {
197    match val {
198        SsaVal::Raw(v, _) => v,
199        SsaVal::HeapPtr(v) => builder.ins().load(types::F64, MemFlags::trusted(), v, LIT_VALUE_OFFSET),
200    }
201}