Skip to main content

tidepool_codegen/emit/
primop.rs

1use super::*;
2use crate::alloc::emit_alloc_fast_path;
3use crate::emit::{EmitError, SsaVal};
4use crate::pipeline::CodegenPipeline;
5use cranelift_codegen::ir::{
6    self, condcodes::FloatCC, condcodes::IntCC, types, AbiParam, BlockArg, InstBuilder, MemFlags,
7    Signature, Value,
8};
9use cranelift_frontend::FunctionBuilder;
10use cranelift_module::Linkage;
11use cranelift_module::Module;
12use tidepool_heap::layout;
13use tidepool_repr::PrimOpKind;
14
15/// Emit a zero-divisor guard: if `divisor == 0`, trap; otherwise fall through.
16fn emit_div_zero_check(builder: &mut FunctionBuilder, divisor: Value) {
17    let zero = builder.ins().iconst(types::I64, 0);
18    let is_zero = builder.ins().icmp(IntCC::Equal, divisor, zero);
19    let ok_block = builder.create_block();
20    let trap_block = builder.create_block();
21    builder.ins().brif(is_zero, trap_block, &[], ok_block, &[]);
22
23    builder.switch_to_block(trap_block);
24    builder.seal_block(trap_block);
25    builder
26        .ins()
27        .trap(cranelift_codegen::ir::TrapCode::unwrap_user(3));
28
29    builder.switch_to_block(ok_block);
30    builder.seal_block(ok_block);
31}
32
33/// Emit a primitive operation. Unboxes HeapPtr args, performs the op, returns Raw.
34pub fn emit_primop(
35    sess: &mut EmitSession,
36    builder: &mut FunctionBuilder,
37    op: &PrimOpKind,
38    args: &[SsaVal],
39) -> Result<SsaVal, EmitError> {
40    match op {
41        // Int arithmetic (binary)
42        PrimOpKind::IntAdd => {
43            check_arity(op, 2, args.len())?;
44            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
45            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
46            Ok(SsaVal::Raw(builder.ins().iadd(a, b), LIT_TAG_INT))
47        }
48        PrimOpKind::IntSub => {
49            check_arity(op, 2, args.len())?;
50            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
51            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
52            Ok(SsaVal::Raw(builder.ins().isub(a, b), LIT_TAG_INT))
53        }
54        PrimOpKind::IntMul => {
55            check_arity(op, 2, args.len())?;
56            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
57            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
58            Ok(SsaVal::Raw(builder.ins().imul(a, b), LIT_TAG_INT))
59        }
60        PrimOpKind::IntNegate => {
61            check_arity(op, 1, args.len())?;
62            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
63            Ok(SsaVal::Raw(builder.ins().ineg(a), LIT_TAG_INT))
64        }
65        PrimOpKind::IntQuot => {
66            check_arity(op, 2, args.len())?;
67            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
68            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
69            emit_div_zero_check(builder, b);
70            Ok(SsaVal::Raw(builder.ins().sdiv(a, b), LIT_TAG_INT))
71        }
72        PrimOpKind::IntRem => {
73            check_arity(op, 2, args.len())?;
74            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
75            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
76            emit_div_zero_check(builder, b);
77            Ok(SsaVal::Raw(builder.ins().srem(a, b), LIT_TAG_INT))
78        }
79
80        // Int bitwise
81        PrimOpKind::IntAnd => {
82            check_arity(op, 2, args.len())?;
83            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
84            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
85            Ok(SsaVal::Raw(builder.ins().band(a, b), LIT_TAG_INT))
86        }
87        PrimOpKind::IntOr => {
88            check_arity(op, 2, args.len())?;
89            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
90            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
91            Ok(SsaVal::Raw(builder.ins().bor(a, b), LIT_TAG_INT))
92        }
93        PrimOpKind::IntXor => {
94            check_arity(op, 2, args.len())?;
95            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
96            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
97            Ok(SsaVal::Raw(builder.ins().bxor(a, b), LIT_TAG_INT))
98        }
99        PrimOpKind::IntNot => {
100            check_arity(op, 1, args.len())?;
101            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
102            Ok(SsaVal::Raw(builder.ins().bnot(a), LIT_TAG_INT))
103        }
104
105        // Int shifts
106        PrimOpKind::IntShl => {
107            check_arity(op, 2, args.len())?;
108            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
109            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
110            Ok(SsaVal::Raw(builder.ins().ishl(a, b), LIT_TAG_INT))
111        }
112        PrimOpKind::IntShra => {
113            check_arity(op, 2, args.len())?;
114            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
115            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
116            Ok(SsaVal::Raw(builder.ins().sshr(a, b), LIT_TAG_INT))
117        }
118        PrimOpKind::IntShrl => {
119            check_arity(op, 2, args.len())?;
120            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
121            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
122            Ok(SsaVal::Raw(builder.ins().ushr(a, b), LIT_TAG_INT))
123        }
124
125        // Int comparison \u2192 returns i64 (0=False, 1=True)
126        PrimOpKind::IntEq => emit_int_compare(
127            sess.pipeline,
128            builder,
129            sess.vmctx,
130            op,
131            IntCC::Equal,
132            args,
133            LIT_TAG_INT,
134        ),
135        PrimOpKind::IntNe => emit_int_compare(
136            sess.pipeline,
137            builder,
138            sess.vmctx,
139            op,
140            IntCC::NotEqual,
141            args,
142            LIT_TAG_INT,
143        ),
144        PrimOpKind::IntLt => emit_int_compare(
145            sess.pipeline,
146            builder,
147            sess.vmctx,
148            op,
149            IntCC::SignedLessThan,
150            args,
151            LIT_TAG_INT,
152        ),
153        PrimOpKind::IntLe => emit_int_compare(
154            sess.pipeline,
155            builder,
156            sess.vmctx,
157            op,
158            IntCC::SignedLessThanOrEqual,
159            args,
160            LIT_TAG_INT,
161        ),
162        PrimOpKind::IntGt => emit_int_compare(
163            sess.pipeline,
164            builder,
165            sess.vmctx,
166            op,
167            IntCC::SignedGreaterThan,
168            args,
169            LIT_TAG_INT,
170        ),
171        PrimOpKind::IntGe => emit_int_compare(
172            sess.pipeline,
173            builder,
174            sess.vmctx,
175            op,
176            IntCC::SignedGreaterThanOrEqual,
177            args,
178            LIT_TAG_INT,
179        ),
180
181        // Word arithmetic
182        PrimOpKind::WordAdd => {
183            check_arity(op, 2, args.len())?;
184            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
185            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
186            Ok(SsaVal::Raw(builder.ins().iadd(a, b), LIT_TAG_WORD))
187        }
188        PrimOpKind::WordSub => {
189            check_arity(op, 2, args.len())?;
190            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
191            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
192            Ok(SsaVal::Raw(builder.ins().isub(a, b), LIT_TAG_WORD))
193        }
194        PrimOpKind::WordMul => {
195            check_arity(op, 2, args.len())?;
196            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
197            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
198            Ok(SsaVal::Raw(builder.ins().imul(a, b), LIT_TAG_WORD))
199        }
200
201        PrimOpKind::WordQuot => {
202            check_arity(op, 2, args.len())?;
203            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
204            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
205            emit_div_zero_check(builder, b);
206            Ok(SsaVal::Raw(builder.ins().udiv(a, b), LIT_TAG_WORD))
207        }
208        PrimOpKind::WordRem => {
209            check_arity(op, 2, args.len())?;
210            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
211            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
212            emit_div_zero_check(builder, b);
213            Ok(SsaVal::Raw(builder.ins().urem(a, b), LIT_TAG_WORD))
214        }
215
216        // Word bitwise
217        PrimOpKind::WordAnd => {
218            check_arity(op, 2, args.len())?;
219            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
220            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
221            Ok(SsaVal::Raw(builder.ins().band(a, b), LIT_TAG_WORD))
222        }
223        PrimOpKind::WordOr => {
224            check_arity(op, 2, args.len())?;
225            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
226            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
227            Ok(SsaVal::Raw(builder.ins().bor(a, b), LIT_TAG_WORD))
228        }
229        PrimOpKind::WordXor => {
230            check_arity(op, 2, args.len())?;
231            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
232            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
233            Ok(SsaVal::Raw(builder.ins().bxor(a, b), LIT_TAG_WORD))
234        }
235        PrimOpKind::WordNot => {
236            check_arity(op, 1, args.len())?;
237            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
238            Ok(SsaVal::Raw(builder.ins().bnot(a), LIT_TAG_WORD))
239        }
240
241        // Word shifts
242        PrimOpKind::WordShl => {
243            check_arity(op, 2, args.len())?;
244            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
245            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
246            Ok(SsaVal::Raw(builder.ins().ishl(a, b), LIT_TAG_WORD))
247        }
248        PrimOpKind::WordShrl => {
249            check_arity(op, 2, args.len())?;
250            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
251            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
252            Ok(SsaVal::Raw(builder.ins().ushr(a, b), LIT_TAG_WORD))
253        }
254
255        // Word comparison (unsigned)
256        PrimOpKind::WordEq => emit_int_compare(
257            sess.pipeline,
258            builder,
259            sess.vmctx,
260            op,
261            IntCC::Equal,
262            args,
263            LIT_TAG_INT,
264        ),
265        PrimOpKind::WordNe => emit_int_compare(
266            sess.pipeline,
267            builder,
268            sess.vmctx,
269            op,
270            IntCC::NotEqual,
271            args,
272            LIT_TAG_INT,
273        ),
274        PrimOpKind::WordLt => emit_int_compare(
275            sess.pipeline,
276            builder,
277            sess.vmctx,
278            op,
279            IntCC::UnsignedLessThan,
280            args,
281            LIT_TAG_INT,
282        ),
283        PrimOpKind::WordLe => emit_int_compare(
284            sess.pipeline,
285            builder,
286            sess.vmctx,
287            op,
288            IntCC::UnsignedLessThanOrEqual,
289            args,
290            LIT_TAG_INT,
291        ),
292        PrimOpKind::WordGt => emit_int_compare(
293            sess.pipeline,
294            builder,
295            sess.vmctx,
296            op,
297            IntCC::UnsignedGreaterThan,
298            args,
299            LIT_TAG_INT,
300        ),
301        PrimOpKind::WordGe => emit_int_compare(
302            sess.pipeline,
303            builder,
304            sess.vmctx,
305            op,
306            IntCC::UnsignedGreaterThanOrEqual,
307            args,
308            LIT_TAG_INT,
309        ),
310
311        // Double arithmetic
312        PrimOpKind::DoubleAdd => {
313            check_arity(op, 2, args.len())?;
314            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
315            let b = unbox_double(sess.pipeline, builder, sess.vmctx, args[1]);
316            Ok(SsaVal::Raw(builder.ins().fadd(a, b), LIT_TAG_DOUBLE))
317        }
318        PrimOpKind::DoubleSub => {
319            check_arity(op, 2, args.len())?;
320            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
321            let b = unbox_double(sess.pipeline, builder, sess.vmctx, args[1]);
322            Ok(SsaVal::Raw(builder.ins().fsub(a, b), LIT_TAG_DOUBLE))
323        }
324        PrimOpKind::DoubleMul => {
325            check_arity(op, 2, args.len())?;
326            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
327            let b = unbox_double(sess.pipeline, builder, sess.vmctx, args[1]);
328            Ok(SsaVal::Raw(builder.ins().fmul(a, b), LIT_TAG_DOUBLE))
329        }
330        PrimOpKind::DoubleDiv => {
331            check_arity(op, 2, args.len())?;
332            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
333            let b = unbox_double(sess.pipeline, builder, sess.vmctx, args[1]);
334            Ok(SsaVal::Raw(builder.ins().fdiv(a, b), LIT_TAG_DOUBLE))
335        }
336
337        // Double comparison
338        PrimOpKind::DoubleEq => emit_double_compare(
339            sess.pipeline,
340            builder,
341            sess.vmctx,
342            op,
343            FloatCC::Equal,
344            args,
345            LIT_TAG_INT,
346        ),
347        PrimOpKind::DoubleNe => emit_double_compare(
348            sess.pipeline,
349            builder,
350            sess.vmctx,
351            op,
352            FloatCC::NotEqual,
353            args,
354            LIT_TAG_INT,
355        ),
356        PrimOpKind::DoubleLt => emit_double_compare(
357            sess.pipeline,
358            builder,
359            sess.vmctx,
360            op,
361            FloatCC::LessThan,
362            args,
363            LIT_TAG_INT,
364        ),
365        PrimOpKind::DoubleLe => emit_double_compare(
366            sess.pipeline,
367            builder,
368            sess.vmctx,
369            op,
370            FloatCC::LessThanOrEqual,
371            args,
372            LIT_TAG_INT,
373        ),
374        PrimOpKind::DoubleGt => emit_double_compare(
375            sess.pipeline,
376            builder,
377            sess.vmctx,
378            op,
379            FloatCC::GreaterThan,
380            args,
381            LIT_TAG_INT,
382        ),
383        PrimOpKind::DoubleGe => emit_double_compare(
384            sess.pipeline,
385            builder,
386            sess.vmctx,
387            op,
388            FloatCC::GreaterThanOrEqual,
389            args,
390            LIT_TAG_INT,
391        ),
392
393        // Char comparison
394        PrimOpKind::CharEq => emit_int_compare(
395            sess.pipeline,
396            builder,
397            sess.vmctx,
398            op,
399            IntCC::Equal,
400            args,
401            LIT_TAG_INT,
402        ),
403        PrimOpKind::CharNe => emit_int_compare(
404            sess.pipeline,
405            builder,
406            sess.vmctx,
407            op,
408            IntCC::NotEqual,
409            args,
410            LIT_TAG_INT,
411        ),
412        PrimOpKind::CharLt => emit_int_compare(
413            sess.pipeline,
414            builder,
415            sess.vmctx,
416            op,
417            IntCC::UnsignedLessThan,
418            args,
419            LIT_TAG_INT,
420        ),
421        PrimOpKind::CharLe => emit_int_compare(
422            sess.pipeline,
423            builder,
424            sess.vmctx,
425            op,
426            IntCC::UnsignedLessThanOrEqual,
427            args,
428            LIT_TAG_INT,
429        ),
430        PrimOpKind::CharGt => emit_int_compare(
431            sess.pipeline,
432            builder,
433            sess.vmctx,
434            op,
435            IntCC::UnsignedGreaterThan,
436            args,
437            LIT_TAG_INT,
438        ),
439        PrimOpKind::CharGe => emit_int_compare(
440            sess.pipeline,
441            builder,
442            sess.vmctx,
443            op,
444            IntCC::UnsignedGreaterThanOrEqual,
445            args,
446            LIT_TAG_INT,
447        ),
448
449        // Conversions
450        PrimOpKind::Chr => {
451            check_arity(op, 1, args.len())?;
452            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
453
454            // Validate codepoint range (match interpreter behavior in eval.rs)
455            // Valid: 0..=0xD7FF or 0xE000..=0x10FFFF
456            // Invalid: negative, > 0x10FFFF, or surrogate 0xD800..=0xDFFF
457            let zero = builder.ins().iconst(types::I64, 0);
458            let max_valid = builder.ins().iconst(types::I64, 0x10FFFF);
459            let is_negative = builder.ins().icmp(IntCC::SignedLessThan, v, zero);
460            let is_too_large = builder.ins().icmp(IntCC::SignedGreaterThan, v, max_valid);
461            let surrogate_lo = builder.ins().iconst(types::I64, 0xD800);
462            let surrogate_hi = builder.ins().iconst(types::I64, 0xDFFF);
463            let is_surr_lo = builder
464                .ins()
465                .icmp(IntCC::SignedGreaterThanOrEqual, v, surrogate_lo);
466            let is_surr_hi = builder
467                .ins()
468                .icmp(IntCC::SignedLessThanOrEqual, v, surrogate_hi);
469            let is_surrogate = builder.ins().band(is_surr_lo, is_surr_hi);
470            let out_of_range = builder.ins().bor(is_negative, is_too_large);
471            let is_invalid = builder.ins().bor(out_of_range, is_surrogate);
472            builder
473                .ins()
474                .trapnz(is_invalid, cranelift_codegen::ir::TrapCode::unwrap_user(1));
475
476            Ok(SsaVal::Raw(v, LIT_TAG_CHAR))
477        }
478        PrimOpKind::Ord => {
479            check_arity(op, 1, args.len())?;
480            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
481            Ok(SsaVal::Raw(v, LIT_TAG_INT))
482        }
483        PrimOpKind::Int2Word | PrimOpKind::Word2Int => {
484            check_arity(op, 1, args.len())?;
485            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
486            let tag = if matches!(op, PrimOpKind::Int2Word) {
487                LIT_TAG_WORD
488            } else {
489                LIT_TAG_INT
490            };
491            Ok(SsaVal::Raw(v, tag))
492        }
493        PrimOpKind::Int2Double => {
494            check_arity(op, 1, args.len())?;
495            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
496            Ok(SsaVal::Raw(
497                builder.ins().fcvt_from_sint(types::F64, v),
498                LIT_TAG_DOUBLE,
499            ))
500        }
501        PrimOpKind::Double2Int => {
502            check_arity(op, 1, args.len())?;
503            let v = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
504            Ok(SsaVal::Raw(
505                builder.ins().fcvt_to_sint_sat(types::I64, v),
506                LIT_TAG_INT,
507            ))
508        }
509        PrimOpKind::DecodeDoubleMantissa => {
510            check_arity(op, 1, args.len())?;
511            let d = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
512            let bits = builder.ins().bitcast(types::I64, MemFlags::new(), d);
513            let result = emit_runtime_call(
514                sess.pipeline,
515                builder,
516                "runtime_decode_double_mantissa",
517                &[AbiParam::new(types::I64)],
518                &[AbiParam::new(types::I64)],
519                &[bits],
520            )?;
521            Ok(SsaVal::Raw(result, LIT_TAG_INT))
522        }
523        PrimOpKind::DecodeDoubleExponent => {
524            check_arity(op, 1, args.len())?;
525            let d = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
526            let bits = builder.ins().bitcast(types::I64, MemFlags::new(), d);
527            let result = emit_runtime_call(
528                sess.pipeline,
529                builder,
530                "runtime_decode_double_exponent",
531                &[AbiParam::new(types::I64)],
532                &[AbiParam::new(types::I64)],
533                &[bits],
534            )?;
535            Ok(SsaVal::Raw(result, LIT_TAG_INT))
536        }
537        PrimOpKind::ShowDoubleAddr => {
538            check_arity(op, 1, args.len())?;
539            let d = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
540            let bits = builder.ins().bitcast(types::I64, MemFlags::new(), d);
541            let result = emit_runtime_call(
542                sess.pipeline,
543                builder,
544                "runtime_show_double_addr",
545                &[AbiParam::new(types::I64)],
546                &[AbiParam::new(types::I64)],
547                &[bits],
548            )?;
549            Ok(SsaVal::Raw(result, LIT_TAG_ADDR))
550        }
551        PrimOpKind::Int2Float => {
552            check_arity(op, 1, args.len())?;
553            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
554            Ok(SsaVal::Raw(
555                builder.ins().fcvt_from_sint(types::F32, v),
556                LIT_TAG_FLOAT,
557            ))
558        }
559        PrimOpKind::Float2Int => {
560            check_arity(op, 1, args.len())?;
561            let v = unbox_float(sess.pipeline, builder, sess.vmctx, args[0]);
562            Ok(SsaVal::Raw(
563                builder.ins().fcvt_to_sint_sat(types::I64, v),
564                LIT_TAG_INT,
565            ))
566        }
567        PrimOpKind::Double2Float => {
568            check_arity(op, 1, args.len())?;
569            let v = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
570            Ok(SsaVal::Raw(
571                builder.ins().fdemote(types::F32, v),
572                LIT_TAG_FLOAT,
573            ))
574        }
575        PrimOpKind::Float2Double => {
576            check_arity(op, 1, args.len())?;
577            let v = unbox_float(sess.pipeline, builder, sess.vmctx, args[0]);
578            Ok(SsaVal::Raw(
579                builder.ins().fpromote(types::F64, v),
580                LIT_TAG_DOUBLE,
581            ))
582        }
583
584        // Narrowing
585        PrimOpKind::Narrow8Int => {
586            check_arity(op, 1, args.len())?;
587            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
588            let narrow = builder.ins().ireduce(types::I8, v);
589            Ok(SsaVal::Raw(
590                builder.ins().sextend(types::I64, narrow),
591                LIT_TAG_INT,
592            ))
593        }
594        PrimOpKind::Narrow16Int => {
595            check_arity(op, 1, args.len())?;
596            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
597            let narrow = builder.ins().ireduce(types::I16, v);
598            Ok(SsaVal::Raw(
599                builder.ins().sextend(types::I64, narrow),
600                LIT_TAG_INT,
601            ))
602        }
603        PrimOpKind::Narrow32Int => {
604            check_arity(op, 1, args.len())?;
605            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
606            let narrow = builder.ins().ireduce(types::I32, v);
607            Ok(SsaVal::Raw(
608                builder.ins().sextend(types::I64, narrow),
609                LIT_TAG_INT,
610            ))
611        }
612        PrimOpKind::Narrow8Word => {
613            check_arity(op, 1, args.len())?;
614            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
615            let narrow = builder.ins().ireduce(types::I8, v);
616            Ok(SsaVal::Raw(
617                builder.ins().uextend(types::I64, narrow),
618                LIT_TAG_WORD,
619            ))
620        }
621        PrimOpKind::Narrow16Word => {
622            check_arity(op, 1, args.len())?;
623            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
624            let narrow = builder.ins().ireduce(types::I16, v);
625            Ok(SsaVal::Raw(
626                builder.ins().uextend(types::I64, narrow),
627                LIT_TAG_WORD,
628            ))
629        }
630        PrimOpKind::Narrow32Word => {
631            check_arity(op, 1, args.len())?;
632            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
633            let narrow = builder.ins().ireduce(types::I32, v);
634            Ok(SsaVal::Raw(
635                builder.ins().uextend(types::I64, narrow),
636                LIT_TAG_WORD,
637            ))
638        }
639
640        // Special ops
641        PrimOpKind::DataToTag => {
642            check_arity(op, 1, args.len())?;
643            let obj = args[0].value();
644            let tag = builder
645                .ins()
646                .load(types::I64, MemFlags::trusted(), obj, CON_TAG_OFFSET);
647            Ok(SsaVal::Raw(tag, LIT_TAG_INT))
648        }
649        PrimOpKind::DoubleNegate => {
650            check_arity(op, 1, args.len())?;
651            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
652            Ok(SsaVal::Raw(builder.ins().fneg(a), LIT_TAG_DOUBLE))
653        }
654        PrimOpKind::DoubleFabs => {
655            check_arity(op, 1, args.len())?;
656            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
657            Ok(SsaVal::Raw(builder.ins().fabs(a), LIT_TAG_DOUBLE))
658        }
659        // Double math unary: sqrt, exp, log, trig, etc. All via libm runtime calls.
660        PrimOpKind::DoubleSqrt => {
661            check_arity(op, 1, args.len())?;
662            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
663            Ok(SsaVal::Raw(builder.ins().sqrt(a), LIT_TAG_DOUBLE))
664        }
665        PrimOpKind::DoubleExp
666        | PrimOpKind::DoubleExpM1
667        | PrimOpKind::DoubleLog
668        | PrimOpKind::DoubleLog1P
669        | PrimOpKind::DoubleSin
670        | PrimOpKind::DoubleCos
671        | PrimOpKind::DoubleTan
672        | PrimOpKind::DoubleAsin
673        | PrimOpKind::DoubleAcos
674        | PrimOpKind::DoubleAtan
675        | PrimOpKind::DoubleSinh
676        | PrimOpKind::DoubleCosh
677        | PrimOpKind::DoubleTanh
678        | PrimOpKind::DoubleAsinh
679        | PrimOpKind::DoubleAcosh
680        | PrimOpKind::DoubleAtanh => {
681            check_arity(op, 1, args.len())?;
682            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
683            let fn_name = match op {
684                PrimOpKind::DoubleExp => "runtime_double_exp",
685                PrimOpKind::DoubleExpM1 => "runtime_double_expm1",
686                PrimOpKind::DoubleLog => "runtime_double_log",
687                PrimOpKind::DoubleLog1P => "runtime_double_log1p",
688                PrimOpKind::DoubleSin => "runtime_double_sin",
689                PrimOpKind::DoubleCos => "runtime_double_cos",
690                PrimOpKind::DoubleTan => "runtime_double_tan",
691                PrimOpKind::DoubleAsin => "runtime_double_asin",
692                PrimOpKind::DoubleAcos => "runtime_double_acos",
693                PrimOpKind::DoubleAtan => "runtime_double_atan",
694                PrimOpKind::DoubleSinh => "runtime_double_sinh",
695                PrimOpKind::DoubleCosh => "runtime_double_cosh",
696                PrimOpKind::DoubleTanh => "runtime_double_tanh",
697                PrimOpKind::DoubleAsinh => "runtime_double_asinh",
698                PrimOpKind::DoubleAcosh => "runtime_double_acosh",
699                PrimOpKind::DoubleAtanh => "runtime_double_atanh",
700                _ => {
701                    return Err(EmitError::InternalError(format!(
702                        "unexpected double primop variant: {:?}",
703                        op
704                    )))
705                }
706            };
707            let bits = builder.ins().bitcast(types::I64, MemFlags::new(), a);
708            let result = emit_runtime_call(
709                sess.pipeline,
710                builder,
711                fn_name,
712                &[AbiParam::new(types::I64)],
713                &[AbiParam::new(types::I64)],
714                &[bits],
715            )?;
716            let d = builder.ins().bitcast(types::F64, MemFlags::new(), result);
717            Ok(SsaVal::Raw(d, LIT_TAG_DOUBLE))
718        }
719        PrimOpKind::DoublePower => {
720            check_arity(op, 2, args.len())?;
721            let a = unbox_double(sess.pipeline, builder, sess.vmctx, args[0]);
722            let b = unbox_double(sess.pipeline, builder, sess.vmctx, args[1]);
723            let bits_a = builder.ins().bitcast(types::I64, MemFlags::new(), a);
724            let bits_b = builder.ins().bitcast(types::I64, MemFlags::new(), b);
725            let result = emit_runtime_call(
726                sess.pipeline,
727                builder,
728                "runtime_double_power",
729                &[AbiParam::new(types::I64), AbiParam::new(types::I64)],
730                &[AbiParam::new(types::I64)],
731                &[bits_a, bits_b],
732            )?;
733            let d = builder.ins().bitcast(types::F64, MemFlags::new(), result);
734            Ok(SsaVal::Raw(d, LIT_TAG_DOUBLE))
735        }
736        PrimOpKind::FloatNegate => {
737            check_arity(op, 1, args.len())?;
738            let a = unbox_float(sess.pipeline, builder, sess.vmctx, args[0]);
739            Ok(SsaVal::Raw(builder.ins().fneg(a), LIT_TAG_FLOAT))
740        }
741
742        PrimOpKind::ReallyUnsafePtrEquality => {
743            check_arity(op, 2, args.len())?;
744            Ok(SsaVal::Raw(
745                builder.ins().iconst(types::I64, 0),
746                LIT_TAG_INT,
747            ))
748        }
749
750        PrimOpKind::IndexCharOffAddr => {
751            check_arity(op, 2, args.len())?;
752            let addr = unbox_addr(sess.pipeline, builder, args[0]);
753            let idx = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
754            let effective = builder.ins().iadd(addr, idx);
755            let byte_val = builder.ins().load(types::I8, MemFlags::new(), effective, 0);
756            let char_val = builder.ins().uextend(types::I64, byte_val);
757            Ok(SsaVal::Raw(char_val, LIT_TAG_CHAR))
758        }
759
760        PrimOpKind::PlusAddr => {
761            check_arity(op, 2, args.len())?;
762            let addr = unbox_addr(sess.pipeline, builder, args[0]);
763            let offset = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
764            Ok(SsaVal::Raw(builder.ins().iadd(addr, offset), LIT_TAG_ADDR))
765        }
766
767        // ---------------------------------------------------------------
768        // Int64/Word64/Word8 \u2014 on 64-bit, these are just Int#/Word# with
769        // different tags. GHC treats them identically at runtime.
770        // ---------------------------------------------------------------
771
772        // Int64 arithmetic
773        PrimOpKind::Int64Add => {
774            check_arity(op, 2, args.len())?;
775            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
776            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
777            Ok(SsaVal::Raw(builder.ins().iadd(a, b), LIT_TAG_INT))
778        }
779        PrimOpKind::Int64Sub => {
780            check_arity(op, 2, args.len())?;
781            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
782            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
783            Ok(SsaVal::Raw(builder.ins().isub(a, b), LIT_TAG_INT))
784        }
785        PrimOpKind::Int64Mul => {
786            check_arity(op, 2, args.len())?;
787            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
788            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
789            Ok(SsaVal::Raw(builder.ins().imul(a, b), LIT_TAG_INT))
790        }
791        PrimOpKind::Int64Negate => {
792            check_arity(op, 1, args.len())?;
793            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
794            Ok(SsaVal::Raw(builder.ins().ineg(a), LIT_TAG_INT))
795        }
796        PrimOpKind::Int64Shl => {
797            check_arity(op, 2, args.len())?;
798            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
799            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
800            Ok(SsaVal::Raw(builder.ins().ishl(a, b), LIT_TAG_INT))
801        }
802        PrimOpKind::Int64Shra => {
803            check_arity(op, 2, args.len())?;
804            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
805            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
806            Ok(SsaVal::Raw(builder.ins().sshr(a, b), LIT_TAG_INT))
807        }
808
809        // Int64 comparison
810        PrimOpKind::Int64Lt => emit_int_compare(
811            sess.pipeline,
812            builder,
813            sess.vmctx,
814            op,
815            IntCC::SignedLessThan,
816            args,
817            LIT_TAG_INT,
818        ),
819        PrimOpKind::Int64Le => emit_int_compare(
820            sess.pipeline,
821            builder,
822            sess.vmctx,
823            op,
824            IntCC::SignedLessThanOrEqual,
825            args,
826            LIT_TAG_INT,
827        ),
828        PrimOpKind::Int64Gt => emit_int_compare(
829            sess.pipeline,
830            builder,
831            sess.vmctx,
832            op,
833            IntCC::SignedGreaterThan,
834            args,
835            LIT_TAG_INT,
836        ),
837        PrimOpKind::Int64Ge => emit_int_compare(
838            sess.pipeline,
839            builder,
840            sess.vmctx,
841            op,
842            IntCC::SignedGreaterThanOrEqual,
843            args,
844            LIT_TAG_INT,
845        ),
846
847        // Word64 arithmetic/bitwise
848        PrimOpKind::Word64And => {
849            check_arity(op, 2, args.len())?;
850            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
851            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
852            Ok(SsaVal::Raw(builder.ins().band(a, b), LIT_TAG_WORD))
853        }
854        PrimOpKind::Word64Shl => {
855            check_arity(op, 2, args.len())?;
856            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
857            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
858            Ok(SsaVal::Raw(builder.ins().ishl(a, b), LIT_TAG_WORD))
859        }
860        PrimOpKind::Word64Or => {
861            check_arity(op, 2, args.len())?;
862            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
863            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
864            Ok(SsaVal::Raw(builder.ins().bor(a, b), LIT_TAG_WORD))
865        }
866
867        // Conversions between sized int/word types (no-ops on 64-bit)
868        PrimOpKind::Word64ToInt64 | PrimOpKind::Int64ToInt | PrimOpKind::Int64ToWord64 => {
869            check_arity(op, 1, args.len())?;
870            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
871            Ok(SsaVal::Raw(v, LIT_TAG_INT))
872        }
873        PrimOpKind::IntToInt64 => {
874            check_arity(op, 1, args.len())?;
875            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
876            Ok(SsaVal::Raw(v, LIT_TAG_INT))
877        }
878        PrimOpKind::Word8ToWord => {
879            check_arity(op, 1, args.len())?;
880            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
881            Ok(SsaVal::Raw(v, LIT_TAG_WORD))
882        }
883        PrimOpKind::WordToWord8 => {
884            // wordToWord8# :: Word# -> Word8#
885            // Narrow to 8 bits
886            check_arity(op, 1, args.len())?;
887            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
888            let mask = builder.ins().iconst(types::I64, 0xFF);
889            let narrow = builder.ins().band(v, mask);
890            Ok(SsaVal::Raw(narrow, LIT_TAG_WORD))
891        }
892
893        // Word8 arithmetic/comparison
894        PrimOpKind::Word8Add => {
895            check_arity(op, 2, args.len())?;
896            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
897            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
898            let sum = builder.ins().iadd(a, b);
899            Ok(SsaVal::Raw(builder.ins().band_imm(sum, 0xFF), LIT_TAG_WORD))
900        }
901        PrimOpKind::Word8Sub => {
902            check_arity(op, 2, args.len())?;
903            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
904            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
905            let diff = builder.ins().isub(a, b);
906            Ok(SsaVal::Raw(
907                builder.ins().band_imm(diff, 0xFF),
908                LIT_TAG_WORD,
909            ))
910        }
911        PrimOpKind::Word8Lt => emit_int_compare(
912            sess.pipeline,
913            builder,
914            sess.vmctx,
915            op,
916            IntCC::UnsignedLessThan,
917            args,
918            LIT_TAG_INT,
919        ),
920        PrimOpKind::Word8Le => emit_int_compare(
921            sess.pipeline,
922            builder,
923            sess.vmctx,
924            op,
925            IntCC::UnsignedLessThanOrEqual,
926            args,
927            LIT_TAG_INT,
928        ),
929        PrimOpKind::Word8Ge => emit_int_compare(
930            sess.pipeline,
931            builder,
932            sess.vmctx,
933            op,
934            IntCC::UnsignedGreaterThanOrEqual,
935            args,
936            LIT_TAG_INT,
937        ),
938
939        // ---------------------------------------------------------------
940        // Carry/overflow arithmetic
941        // ---------------------------------------------------------------
942
943        // addIntC# :: Int# -> Int# -> (# Int#, Int# #)
944        // We emit just the value or carry depending on which variant.
945        PrimOpKind::AddIntCVal => {
946            check_arity(op, 2, args.len())?;
947            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
948            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
949            Ok(SsaVal::Raw(builder.ins().iadd(a, b), LIT_TAG_INT))
950        }
951        PrimOpKind::AddIntCCarry => {
952            check_arity(op, 2, args.len())?;
953            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
954            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
955            let sum = builder.ins().iadd(a, b);
956            // Signed overflow: (a > 0 && b > 0 && sum < 0) || (a < 0 && b < 0 && sum >= 0)
957            // Simplified: overflow if sign(a) == sign(b) && sign(sum) != sign(a)
958            let xor_ab = builder.ins().bxor(a, b);
959            let xor_as = builder.ins().bxor(a, sum);
960            // If signs of a,b are same (xor_ab bit 63 = 0) AND sign of sum differs from a (xor_as bit 63 = 1)
961            let not_xor_ab = builder.ins().bnot(xor_ab);
962            let overflow_bits = builder.ins().band(not_xor_ab, xor_as);
963            // Shift bit 63 to bit 0
964            let shifted = builder.ins().ushr_imm(overflow_bits, 63);
965            Ok(SsaVal::Raw(shifted, LIT_TAG_INT))
966        }
967
968        // subWordC# :: Word# -> Word# -> (# Word#, Int# #)
969        PrimOpKind::SubWordCVal => {
970            check_arity(op, 2, args.len())?;
971            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
972            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
973            Ok(SsaVal::Raw(builder.ins().isub(a, b), LIT_TAG_WORD))
974        }
975        PrimOpKind::SubWordCCarry => {
976            check_arity(op, 2, args.len())?;
977            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
978            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
979            // Borrow if a < b (unsigned)
980            let borrow = builder.ins().icmp(IntCC::UnsignedLessThan, a, b);
981            Ok(SsaVal::Raw(
982                builder.ins().uextend(types::I64, borrow),
983                LIT_TAG_INT,
984            ))
985        }
986
987        // addWordC# :: Word# -> Word# -> (# Word#, Int# #)
988        PrimOpKind::AddWordCVal => {
989            check_arity(op, 2, args.len())?;
990            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
991            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
992            Ok(SsaVal::Raw(builder.ins().iadd(a, b), LIT_TAG_WORD))
993        }
994        PrimOpKind::AddWordCCarry => {
995            check_arity(op, 2, args.len())?;
996            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
997            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
998            let sum = builder.ins().iadd(a, b);
999            // Carry if sum < a (unsigned)
1000            let carry = builder.ins().icmp(IntCC::UnsignedLessThan, sum, a);
1001            Ok(SsaVal::Raw(
1002                builder.ins().uextend(types::I64, carry),
1003                LIT_TAG_INT,
1004            ))
1005        }
1006
1007        // timesInt2# :: Int# -> Int# -> (# Int#, Int#, Int# #)
1008        // Signed widening multiply: (hi, lo, overflow)
1009        PrimOpKind::TimesInt2Hi => {
1010            check_arity(op, 2, args.len())?;
1011            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1012            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1013            Ok(SsaVal::Raw(builder.ins().smulhi(a, b), LIT_TAG_INT))
1014        }
1015        PrimOpKind::TimesInt2Lo => {
1016            check_arity(op, 2, args.len())?;
1017            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1018            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1019            Ok(SsaVal::Raw(builder.ins().imul(a, b), LIT_TAG_INT))
1020        }
1021        PrimOpKind::TimesInt2Overflow => {
1022            check_arity(op, 2, args.len())?;
1023            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1024            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1025            // Overflow if smulhi(a,b) != (imul(a,b) >>s 63)
1026            // i.e., the high word differs from sign-extending the low word
1027            let hi = builder.ins().smulhi(a, b);
1028            let lo = builder.ins().imul(a, b);
1029            let lo_sign = builder.ins().sshr_imm(lo, 63);
1030            let overflow = builder.ins().icmp(IntCC::NotEqual, hi, lo_sign);
1031            Ok(SsaVal::Raw(
1032                builder.ins().uextend(types::I64, overflow),
1033                LIT_TAG_INT,
1034            ))
1035        }
1036
1037        // timesWord2# :: Word# -> Word# -> (# Word#, Word# #)
1038        // High and low words of 128-bit multiply
1039        PrimOpKind::TimesWord2Hi => {
1040            check_arity(op, 2, args.len())?;
1041            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1042            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1043            Ok(SsaVal::Raw(builder.ins().umulhi(a, b), LIT_TAG_WORD))
1044        }
1045        PrimOpKind::TimesWord2Lo => {
1046            check_arity(op, 2, args.len())?;
1047            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1048            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1049            Ok(SsaVal::Raw(builder.ins().imul(a, b), LIT_TAG_WORD))
1050        }
1051
1052        // quotRemWord# :: Word# -> Word# -> (# Word#, Word# #)
1053        PrimOpKind::QuotRemWordVal => {
1054            check_arity(op, 2, args.len())?;
1055            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1056            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1057            emit_div_zero_check(builder, b);
1058            Ok(SsaVal::Raw(builder.ins().udiv(a, b), LIT_TAG_WORD))
1059        }
1060        PrimOpKind::QuotRemWordRem => {
1061            check_arity(op, 2, args.len())?;
1062            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1063            let b = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1064            emit_div_zero_check(builder, b);
1065            Ok(SsaVal::Raw(builder.ins().urem(a, b), LIT_TAG_WORD))
1066        }
1067
1068        // ---------------------------------------------------------------
1069        // ByteArray# primops \u2014 mutable byte arrays for Data.Text etc.
1070        // ByteArray is stored as a Lit with LIT_TAG_BYTEARRAY, value = ptr
1071        // to malloc'd buffer: [u64 length][u8 bytes...]
1072        // ---------------------------------------------------------------
1073        PrimOpKind::NewByteArray => {
1074            // newByteArray# :: Int# -> State# s -> (# State# s, MutableByteArray# s #)
1075            // State# token may or may not be passed (1 or 2 args)
1076            let size = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1077            let ba_ptr = emit_runtime_call(
1078                sess.pipeline,
1079                builder,
1080                "runtime_new_byte_array",
1081                &[AbiParam::new(types::I64)],
1082                &[AbiParam::new(types::I64)],
1083                &[size],
1084            )?;
1085            // Wrap in a Lit on the managed heap
1086            Ok(emit_lit_bytearray(
1087                builder,
1088                sess.vmctx,
1089                sess.gc_sig,
1090                sess.oom_func,
1091                ba_ptr,
1092            ))
1093        }
1094
1095        PrimOpKind::UnsafeFreezeByteArray => {
1096            // unsafeFreezeByteArray# :: MutableByteArray# s -> State# s -> (# State# s, ByteArray# #)
1097            // Identity \u2014 mutable and immutable have the same representation
1098            Ok(args[0])
1099        }
1100
1101        PrimOpKind::SizeofByteArray | PrimOpKind::SizeofMutableByteArray => {
1102            // sizeofByteArray# :: ByteArray# -> Int#
1103            let ba_ptr = unbox_bytearray(sess.pipeline, builder, args[0]);
1104            // Read u64 length from offset 0
1105            let len = builder.ins().load(types::I64, MemFlags::new(), ba_ptr, 0);
1106            Ok(SsaVal::Raw(len, LIT_TAG_INT))
1107        }
1108
1109        PrimOpKind::ReadWord8Array | PrimOpKind::IndexWord8Array => {
1110            // readWord8Array# :: MutableByteArray# s -> Int# -> State# s -> (# State# s, Word# #)
1111            let ba_ptr = unbox_bytearray(sess.pipeline, builder, args[0]);
1112            let idx = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1113            // Data starts at offset 8
1114            let base = builder.ins().iadd_imm(ba_ptr, 8);
1115            let effective = builder.ins().iadd(base, idx);
1116            let byte = builder.ins().load(types::I8, MemFlags::new(), effective, 0);
1117            let val = builder.ins().uextend(types::I64, byte);
1118            Ok(SsaVal::Raw(val, LIT_TAG_WORD))
1119        }
1120
1121        PrimOpKind::WriteWord8Array => {
1122            // writeWord8Array# :: MutableByteArray# s -> Int# -> Word# -> State# s -> State# s
1123            let ba_ptr = unbox_bytearray(sess.pipeline, builder, args[0]);
1124            let idx = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1125            let val = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1126            let base = builder.ins().iadd_imm(ba_ptr, 8);
1127            let effective = builder.ins().iadd(base, idx);
1128            let byte = builder.ins().ireduce(types::I8, val);
1129            builder.ins().store(MemFlags::new(), byte, effective, 0);
1130            // Return dummy state token
1131            Ok(SsaVal::Raw(
1132                builder.ins().iconst(types::I64, 0),
1133                LIT_TAG_INT,
1134            ))
1135        }
1136
1137        PrimOpKind::IndexWordArray | PrimOpKind::ReadWordArray => {
1138            // indexWordArray# :: ByteArray# -> Int# -> Word#
1139            let ba_ptr = unbox_bytearray(sess.pipeline, builder, args[0]);
1140            let idx = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1141            let base = builder.ins().iadd_imm(ba_ptr, 8);
1142            // Word-sized (8 bytes) indexing
1143            let byte_offset = builder.ins().imul_imm(idx, 8);
1144            let effective = builder.ins().iadd(base, byte_offset);
1145            let word = builder
1146                .ins()
1147                .load(types::I64, MemFlags::new(), effective, 0);
1148            Ok(SsaVal::Raw(word, LIT_TAG_WORD))
1149        }
1150
1151        PrimOpKind::WriteWordArray => {
1152            // writeWordArray# :: MutableByteArray# s -> Int# -> Word# -> State# s -> State# s
1153            let ba_ptr = unbox_bytearray(sess.pipeline, builder, args[0]);
1154            let idx = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1155            let val = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1156            let base = builder.ins().iadd_imm(ba_ptr, 8);
1157            let byte_offset = builder.ins().imul_imm(idx, 8);
1158            let effective = builder.ins().iadd(base, byte_offset);
1159            builder.ins().store(MemFlags::new(), val, effective, 0);
1160            Ok(SsaVal::Raw(
1161                builder.ins().iconst(types::I64, 0),
1162                LIT_TAG_INT,
1163            ))
1164        }
1165
1166        PrimOpKind::CopyAddrToByteArray => {
1167            // copyAddrToByteArray# :: Addr# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
1168            let src = unbox_addr(sess.pipeline, builder, args[0]);
1169            let dest_ba = unbox_bytearray(sess.pipeline, builder, args[1]);
1170            let dest_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1171            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1172            let _ = emit_runtime_call(
1173                sess.pipeline,
1174                builder,
1175                "runtime_copy_addr_to_byte_array",
1176                &[
1177                    AbiParam::new(types::I64),
1178                    AbiParam::new(types::I64),
1179                    AbiParam::new(types::I64),
1180                    AbiParam::new(types::I64),
1181                ],
1182                &[],
1183                &[src, dest_ba, dest_off, len],
1184            )?;
1185            Ok(SsaVal::Raw(
1186                builder.ins().iconst(types::I64, 0),
1187                LIT_TAG_INT,
1188            ))
1189        }
1190
1191        PrimOpKind::SetByteArray => {
1192            // setByteArray# :: MutableByteArray# s -> Int# -> Int# -> Int# -> State# s -> State# s
1193            let ba = unbox_bytearray(sess.pipeline, builder, args[0]);
1194            let off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1195            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1196            let val = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1197            let _ = emit_runtime_call(
1198                sess.pipeline,
1199                builder,
1200                "runtime_set_byte_array",
1201                &[
1202                    AbiParam::new(types::I64),
1203                    AbiParam::new(types::I64),
1204                    AbiParam::new(types::I64),
1205                    AbiParam::new(types::I64),
1206                ],
1207                &[],
1208                &[ba, off, len, val],
1209            )?;
1210            Ok(SsaVal::Raw(
1211                builder.ins().iconst(types::I64, 0),
1212                LIT_TAG_INT,
1213            ))
1214        }
1215
1216        PrimOpKind::ShrinkMutableByteArray => {
1217            // shrinkMutableByteArray# :: MutableByteArray# s -> Int# -> State# s -> State# s
1218            let ba = unbox_bytearray(sess.pipeline, builder, args[0]);
1219            let new_size = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1220            let _ = emit_runtime_call(
1221                sess.pipeline,
1222                builder,
1223                "runtime_shrink_byte_array",
1224                &[AbiParam::new(types::I64), AbiParam::new(types::I64)],
1225                &[],
1226                &[ba, new_size],
1227            )?;
1228            Ok(SsaVal::Raw(
1229                builder.ins().iconst(types::I64, 0),
1230                LIT_TAG_INT,
1231            ))
1232        }
1233        PrimOpKind::ResizeMutableByteArray => {
1234            // resizeMutableByteArray# :: MutableByteArray# s -> Int# -> State# s
1235            //   -> (# State# s, MutableByteArray# s #)
1236            // Returns the (possibly reallocated) byte array pointer.
1237            let ba = unbox_bytearray(sess.pipeline, builder, args[0]);
1238            let new_size = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1239            let result = emit_runtime_call(
1240                sess.pipeline,
1241                builder,
1242                "runtime_resize_byte_array",
1243                &[AbiParam::new(types::I64), AbiParam::new(types::I64)],
1244                &[AbiParam::new(types::I64)],
1245                &[ba, new_size],
1246            )?;
1247            Ok(SsaVal::Raw(result, LIT_TAG_BYTEARRAY))
1248        }
1249
1250        PrimOpKind::CopyByteArray => {
1251            // copyByteArray# :: ByteArray# -> Int# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
1252            // src, src_off, dest, dest_off, len
1253            let src = unbox_bytearray(sess.pipeline, builder, args[0]);
1254            let src_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1255            let dest = unbox_bytearray(sess.pipeline, builder, args[2]);
1256            let dest_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1257            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[4]);
1258            let _ = emit_runtime_call(
1259                sess.pipeline,
1260                builder,
1261                "runtime_copy_byte_array",
1262                &[
1263                    AbiParam::new(types::I64),
1264                    AbiParam::new(types::I64),
1265                    AbiParam::new(types::I64),
1266                    AbiParam::new(types::I64),
1267                    AbiParam::new(types::I64),
1268                ],
1269                &[],
1270                &[src, src_off, dest, dest_off, len],
1271            )?;
1272            Ok(SsaVal::Raw(
1273                builder.ins().iconst(types::I64, 0),
1274                LIT_TAG_INT,
1275            ))
1276        }
1277        PrimOpKind::CopyMutableByteArray => {
1278            // copyMutableByteArray# \u2014 same args as CopyByteArray
1279            let src = unbox_bytearray(sess.pipeline, builder, args[0]);
1280            let src_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1281            let dest = unbox_bytearray(sess.pipeline, builder, args[2]);
1282            let dest_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1283            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[4]);
1284            let _ = emit_runtime_call(
1285                sess.pipeline,
1286                builder,
1287                "runtime_copy_byte_array",
1288                &[
1289                    AbiParam::new(types::I64),
1290                    AbiParam::new(types::I64),
1291                    AbiParam::new(types::I64),
1292                    AbiParam::new(types::I64),
1293                    AbiParam::new(types::I64),
1294                ],
1295                &[],
1296                &[src, src_off, dest, dest_off, len],
1297            )?;
1298            Ok(SsaVal::Raw(
1299                builder.ins().iconst(types::I64, 0),
1300                LIT_TAG_INT,
1301            ))
1302        }
1303        PrimOpKind::CompareByteArrays => {
1304            // compareByteArrays# :: ByteArray# -> Int# -> ByteArray# -> Int# -> Int# -> Int#
1305            if args.len() != 5 {
1306                return Err(EmitError::InvalidArity(
1307                    PrimOpKind::CompareByteArrays,
1308                    5,
1309                    args.len(),
1310                ));
1311            }
1312            let a = unbox_bytearray(sess.pipeline, builder, args[0]);
1313            let a_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1314            let b = unbox_bytearray(sess.pipeline, builder, args[2]);
1315            let b_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1316            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[4]);
1317            let result = emit_runtime_call(
1318                sess.pipeline,
1319                builder,
1320                "runtime_compare_byte_arrays",
1321                &[
1322                    AbiParam::new(types::I64),
1323                    AbiParam::new(types::I64),
1324                    AbiParam::new(types::I64),
1325                    AbiParam::new(types::I64),
1326                    AbiParam::new(types::I64),
1327                ],
1328                &[AbiParam::new(types::I64)],
1329                &[a, a_off, b, b_off, len],
1330            )?;
1331            Ok(SsaVal::Raw(result, LIT_TAG_INT))
1332        }
1333        PrimOpKind::IndexWord8OffAddr => {
1334            // indexWord8OffAddr# :: Addr# -> Int# -> Word#
1335            let addr = unbox_addr(sess.pipeline, builder, args[0]);
1336            let off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1337            let ptr = builder.ins().iadd(addr, off);
1338            let byte = builder.ins().load(types::I8, MemFlags::trusted(), ptr, 0);
1339            let word = builder.ins().uextend(types::I64, byte);
1340            Ok(SsaVal::Raw(word, LIT_TAG_WORD))
1341        }
1342        PrimOpKind::Clz8 => {
1343            // clz8# :: Word# -> Word#
1344            check_arity(op, 1, args.len())?;
1345            let v = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1346            let narrow = builder.ins().ireduce(types::I8, v);
1347            let clz8 = builder.ins().clz(narrow);
1348            let result = builder.ins().uextend(types::I64, clz8);
1349            Ok(SsaVal::Raw(result, LIT_TAG_WORD))
1350        }
1351        PrimOpKind::Raise => {
1352            // raise# :: a -> b \u2014 always errors
1353            let kind = builder.ins().iconst(types::I64, 2); // 2 = UserError
1354            let _ = emit_runtime_call(
1355                sess.pipeline,
1356                builder,
1357                "runtime_error",
1358                &[AbiParam::new(types::I64)],
1359                &[AbiParam::new(types::I64)],
1360                &[kind],
1361            )?;
1362            Ok(SsaVal::Raw(
1363                builder.ins().iconst(types::I64, 0),
1364                LIT_TAG_INT,
1365            ))
1366        }
1367        PrimOpKind::FfiStrlen => {
1368            // strlen :: Addr# -> Int#
1369            let addr = unbox_addr(sess.pipeline, builder, args[0]);
1370            let result = emit_runtime_call(
1371                sess.pipeline,
1372                builder,
1373                "runtime_strlen",
1374                &[AbiParam::new(types::I64)],
1375                &[AbiParam::new(types::I64)],
1376                &[addr],
1377            )?;
1378            Ok(SsaVal::Raw(result, LIT_TAG_INT))
1379        }
1380        PrimOpKind::FfiTextMeasureOff => {
1381            // _hs_text_measure_off :: ByteArray# -> CSize -> CSize -> CSize -> CSsize
1382            let ba = unbox_bytearray(sess.pipeline, builder, args[0]);
1383            let data_ptr = builder.ins().iadd_imm(ba, 8); // skip length prefix
1384            let off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1385            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1386            let cnt = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1387            let result = emit_runtime_call(
1388                sess.pipeline,
1389                builder,
1390                "runtime_text_measure_off",
1391                &[
1392                    AbiParam::new(types::I64),
1393                    AbiParam::new(types::I64),
1394                    AbiParam::new(types::I64),
1395                    AbiParam::new(types::I64),
1396                ],
1397                &[AbiParam::new(types::I64)],
1398                &[data_ptr, off, len, cnt],
1399            )?;
1400            Ok(SsaVal::Raw(result, LIT_TAG_INT))
1401        }
1402        PrimOpKind::FfiTextMemchr => {
1403            // _hs_text_memchr :: ByteArray# -> CSize -> CSize -> Word8 -> CSsize
1404            let ba = unbox_bytearray(sess.pipeline, builder, args[0]);
1405            let data_ptr = builder.ins().iadd_imm(ba, 8); // skip length prefix
1406            let off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1407            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1408            let needle = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1409            let result = emit_runtime_call(
1410                sess.pipeline,
1411                builder,
1412                "runtime_text_memchr",
1413                &[
1414                    AbiParam::new(types::I64),
1415                    AbiParam::new(types::I64),
1416                    AbiParam::new(types::I64),
1417                    AbiParam::new(types::I64),
1418                ],
1419                &[AbiParam::new(types::I64)],
1420                &[data_ptr, off, len, needle],
1421            )?;
1422            Ok(SsaVal::Raw(result, LIT_TAG_INT))
1423        }
1424        PrimOpKind::FfiTextReverse => {
1425            // _hs_text_reverse :: MutableByteArray# -> ByteArray# -> CSize -> CSize -> ()
1426            let dest_ba = unbox_bytearray(sess.pipeline, builder, args[0]);
1427            let dest_ptr = builder.ins().iadd_imm(dest_ba, 8); // skip length prefix
1428            let src_ba = unbox_bytearray(sess.pipeline, builder, args[1]);
1429            let src_ptr = builder.ins().iadd_imm(src_ba, 8); // skip length prefix
1430            let off = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1431            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1432            let _ = emit_runtime_call(
1433                sess.pipeline,
1434                builder,
1435                "runtime_text_reverse",
1436                &[
1437                    AbiParam::new(types::I64),
1438                    AbiParam::new(types::I64),
1439                    AbiParam::new(types::I64),
1440                    AbiParam::new(types::I64),
1441                ],
1442                &[],
1443                &[dest_ptr, src_ptr, off, len],
1444            )?;
1445            Ok(SsaVal::Raw(
1446                builder.ins().iconst(types::I64, 0),
1447                LIT_TAG_INT,
1448            ))
1449        }
1450
1451        // Float arithmetic + comparison
1452        PrimOpKind::FloatAdd => {
1453            check_arity(op, 2, args.len())?;
1454            let a = unbox_float(sess.pipeline, builder, sess.vmctx, args[0]);
1455            let b = unbox_float(sess.pipeline, builder, sess.vmctx, args[1]);
1456            Ok(SsaVal::Raw(builder.ins().fadd(a, b), LIT_TAG_FLOAT))
1457        }
1458        PrimOpKind::FloatSub => {
1459            check_arity(op, 2, args.len())?;
1460            let a = unbox_float(sess.pipeline, builder, sess.vmctx, args[0]);
1461            let b = unbox_float(sess.pipeline, builder, sess.vmctx, args[1]);
1462            Ok(SsaVal::Raw(builder.ins().fsub(a, b), LIT_TAG_FLOAT))
1463        }
1464        PrimOpKind::FloatMul => {
1465            check_arity(op, 2, args.len())?;
1466            let a = unbox_float(sess.pipeline, builder, sess.vmctx, args[0]);
1467            let b = unbox_float(sess.pipeline, builder, sess.vmctx, args[1]);
1468            Ok(SsaVal::Raw(builder.ins().fmul(a, b), LIT_TAG_FLOAT))
1469        }
1470        PrimOpKind::FloatDiv => {
1471            check_arity(op, 2, args.len())?;
1472            let a = unbox_float(sess.pipeline, builder, sess.vmctx, args[0]);
1473            let b = unbox_float(sess.pipeline, builder, sess.vmctx, args[1]);
1474            Ok(SsaVal::Raw(builder.ins().fdiv(a, b), LIT_TAG_FLOAT))
1475        }
1476        PrimOpKind::FloatEq => emit_f32_compare(
1477            sess.pipeline,
1478            builder,
1479            sess.vmctx,
1480            op,
1481            FloatCC::Equal,
1482            args,
1483            LIT_TAG_INT,
1484        ),
1485        PrimOpKind::FloatNe => emit_f32_compare(
1486            sess.pipeline,
1487            builder,
1488            sess.vmctx,
1489            op,
1490            FloatCC::NotEqual,
1491            args,
1492            LIT_TAG_INT,
1493        ),
1494        PrimOpKind::FloatLt => emit_f32_compare(
1495            sess.pipeline,
1496            builder,
1497            sess.vmctx,
1498            op,
1499            FloatCC::LessThan,
1500            args,
1501            LIT_TAG_INT,
1502        ),
1503        PrimOpKind::FloatLe => emit_f32_compare(
1504            sess.pipeline,
1505            builder,
1506            sess.vmctx,
1507            op,
1508            FloatCC::LessThanOrEqual,
1509            args,
1510            LIT_TAG_INT,
1511        ),
1512        PrimOpKind::FloatGt => emit_f32_compare(
1513            sess.pipeline,
1514            builder,
1515            sess.vmctx,
1516            op,
1517            FloatCC::GreaterThan,
1518            args,
1519            LIT_TAG_INT,
1520        ),
1521        PrimOpKind::FloatGe => emit_f32_compare(
1522            sess.pipeline,
1523            builder,
1524            sess.vmctx,
1525            op,
1526            FloatCC::GreaterThanOrEqual,
1527            args,
1528            LIT_TAG_INT,
1529        ),
1530
1531        PrimOpKind::TagToEnum | PrimOpKind::SeqOp => {
1532            Err(EmitError::NotYetImplemented(format!("{:?}", op)))
1533        }
1534
1535        // ---------------------------------------------------------------
1536        // SmallArray# / Array# primops \u2014 boxed pointer arrays
1537        // Layout: [u64 length][ptr0][ptr1]...[ptrN-1]
1538        // Stored in Lit with LIT_TAG_SMALLARRAY (8) or LIT_TAG_ARRAY (9)
1539        // ---------------------------------------------------------------
1540        PrimOpKind::NewSmallArray | PrimOpKind::NewArray => {
1541            // newSmallArray# :: Int# -> a -> State# -> (# State#, SmallMutableArray# s a #)
1542            let size = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1543            let init_ptr = args[1].value();
1544            let arr_ptr = emit_runtime_call(
1545                sess.pipeline,
1546                builder,
1547                "runtime_new_boxed_array",
1548                &[AbiParam::new(types::I64), AbiParam::new(types::I64)],
1549                &[AbiParam::new(types::I64)],
1550                &[size, init_ptr],
1551            )?;
1552            let lit_tag = if matches!(op, PrimOpKind::NewSmallArray) {
1553                LIT_TAG_SMALLARRAY
1554            } else {
1555                LIT_TAG_ARRAY
1556            };
1557            Ok(emit_lit_boxed_array(
1558                builder,
1559                sess.vmctx,
1560                sess.gc_sig,
1561                sess.oom_func,
1562                arr_ptr,
1563                lit_tag,
1564            ))
1565        }
1566
1567        PrimOpKind::ReadSmallArray
1568        | PrimOpKind::IndexSmallArray
1569        | PrimOpKind::ReadArray
1570        | PrimOpKind::IndexArray => {
1571            // readSmallArray# :: SmallMutableArray# s a -> Int# -> State# -> (# State#, a #)
1572            // indexSmallArray# :: SmallArray# a -> Int# -> (# a #)
1573            if args.len() < 2 {
1574                return Err(EmitError::NotYetImplemented(format!(
1575                    "{:?}: expected >=2 args, got {} (args: {:?})",
1576                    op,
1577                    args.len(),
1578                    args
1579                )));
1580            }
1581            let arr_ptr = unbox_bytearray(sess.pipeline, builder, args[0]);
1582            let idx = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1583            let base = builder.ins().iadd_imm(arr_ptr, 8);
1584            let byte_offset = builder.ins().imul_imm(idx, 8);
1585            let effective = builder.ins().iadd(base, byte_offset);
1586            let loaded = builder
1587                .ins()
1588                .load(types::I64, MemFlags::new(), effective, 0);
1589            builder.declare_value_needs_stack_map(loaded);
1590            Ok(SsaVal::HeapPtr(loaded))
1591        }
1592
1593        PrimOpKind::WriteSmallArray | PrimOpKind::WriteArray => {
1594            // writeSmallArray# :: SmallMutableArray# s a -> Int# -> a -> State# -> State#
1595            let arr_ptr = unbox_bytearray(sess.pipeline, builder, args[0]);
1596            let idx = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1597            let val = args[2].value();
1598            let base = builder.ins().iadd_imm(arr_ptr, 8);
1599            let byte_offset = builder.ins().imul_imm(idx, 8);
1600            let effective = builder.ins().iadd(base, byte_offset);
1601            builder.ins().store(MemFlags::new(), val, effective, 0);
1602            Ok(SsaVal::Raw(
1603                builder.ins().iconst(types::I64, 0),
1604                LIT_TAG_INT,
1605            ))
1606        }
1607
1608        PrimOpKind::SizeofSmallArray
1609        | PrimOpKind::SizeofSmallMutableArray
1610        | PrimOpKind::SizeofArray
1611        | PrimOpKind::SizeofMutableArray => {
1612            // sizeofSmallArray# :: SmallArray# a -> Int#
1613            let arr_ptr = unbox_bytearray(sess.pipeline, builder, args[0]);
1614            let len = builder.ins().load(types::I64, MemFlags::new(), arr_ptr, 0);
1615            Ok(SsaVal::Raw(len, LIT_TAG_INT))
1616        }
1617
1618        PrimOpKind::UnsafeFreezeSmallArray
1619        | PrimOpKind::UnsafeThawSmallArray
1620        | PrimOpKind::UnsafeFreezeArray
1621        | PrimOpKind::UnsafeThawArray => {
1622            // Identity \u2014 mutable and immutable have the same representation
1623            Ok(args[0])
1624        }
1625
1626        PrimOpKind::CopySmallArray
1627        | PrimOpKind::CopySmallMutableArray
1628        | PrimOpKind::CopyArray
1629        | PrimOpKind::CopyMutableArray => {
1630            // copySmallArray# :: src -> src_off -> dest -> dest_off -> len -> State# -> State#
1631            let src = unbox_bytearray(sess.pipeline, builder, args[0]);
1632            let src_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1633            let dest = unbox_bytearray(sess.pipeline, builder, args[2]);
1634            let dest_off = unbox_int(sess.pipeline, builder, sess.vmctx, args[3]);
1635            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[4]);
1636            let _ = emit_runtime_call(
1637                sess.pipeline,
1638                builder,
1639                "runtime_copy_boxed_array",
1640                &[
1641                    AbiParam::new(types::I64),
1642                    AbiParam::new(types::I64),
1643                    AbiParam::new(types::I64),
1644                    AbiParam::new(types::I64),
1645                    AbiParam::new(types::I64),
1646                ],
1647                &[],
1648                &[src, src_off, dest, dest_off, len],
1649            )?;
1650            Ok(SsaVal::Raw(
1651                builder.ins().iconst(types::I64, 0),
1652                LIT_TAG_INT,
1653            ))
1654        }
1655
1656        PrimOpKind::CloneSmallArray | PrimOpKind::CloneSmallMutableArray => {
1657            // cloneSmallArray# :: SmallArray# a -> Int# -> Int# -> SmallArray# a
1658            let arr = unbox_bytearray(sess.pipeline, builder, args[0]);
1659            let off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1660            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1661            let result = emit_runtime_call(
1662                sess.pipeline,
1663                builder,
1664                "runtime_clone_boxed_array",
1665                &[
1666                    AbiParam::new(types::I64),
1667                    AbiParam::new(types::I64),
1668                    AbiParam::new(types::I64),
1669                ],
1670                &[AbiParam::new(types::I64)],
1671                &[arr, off, len],
1672            )?;
1673            Ok(emit_lit_boxed_array(
1674                builder,
1675                sess.vmctx,
1676                sess.gc_sig,
1677                sess.oom_func,
1678                result,
1679                LIT_TAG_SMALLARRAY,
1680            ))
1681        }
1682
1683        PrimOpKind::CloneArray | PrimOpKind::CloneMutableArray => {
1684            let arr = unbox_bytearray(sess.pipeline, builder, args[0]);
1685            let off = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1686            let len = unbox_int(sess.pipeline, builder, sess.vmctx, args[2]);
1687            let result = emit_runtime_call(
1688                sess.pipeline,
1689                builder,
1690                "runtime_clone_boxed_array",
1691                &[
1692                    AbiParam::new(types::I64),
1693                    AbiParam::new(types::I64),
1694                    AbiParam::new(types::I64),
1695                ],
1696                &[AbiParam::new(types::I64)],
1697                &[arr, off, len],
1698            )?;
1699            Ok(emit_lit_boxed_array(
1700                builder,
1701                sess.vmctx,
1702                sess.gc_sig,
1703                sess.oom_func,
1704                result,
1705                LIT_TAG_ARRAY,
1706            ))
1707        }
1708
1709        PrimOpKind::ShrinkSmallMutableArray => {
1710            let arr = unbox_bytearray(sess.pipeline, builder, args[0]);
1711            let new_len = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1712            let _ = emit_runtime_call(
1713                sess.pipeline,
1714                builder,
1715                "runtime_shrink_boxed_array",
1716                &[AbiParam::new(types::I64), AbiParam::new(types::I64)],
1717                &[],
1718                &[arr, new_len],
1719            )?;
1720            Ok(SsaVal::Raw(
1721                builder.ins().iconst(types::I64, 0),
1722                LIT_TAG_INT,
1723            ))
1724        }
1725
1726        PrimOpKind::CasSmallArray => {
1727            // casSmallArray# :: SmallMutableArray# s a -> Int# -> a -> a -> State# s
1728            //   -> (# State# s, Int#, a #)
1729            // Returns (0#, old) if CAS succeeded, (1#, old) if failed.
1730            // We simplify: return the old value (caller checks).
1731            let arr = unbox_bytearray(sess.pipeline, builder, args[0]);
1732            let idx = unbox_int(sess.pipeline, builder, sess.vmctx, args[1]);
1733            let expected = args[2].value();
1734            let new_val = args[3].value();
1735            let old = emit_runtime_call(
1736                sess.pipeline,
1737                builder,
1738                "runtime_cas_boxed_array",
1739                &[
1740                    AbiParam::new(types::I64),
1741                    AbiParam::new(types::I64),
1742                    AbiParam::new(types::I64),
1743                    AbiParam::new(types::I64),
1744                ],
1745                &[AbiParam::new(types::I64)],
1746                &[arr, idx, expected, new_val],
1747            )?;
1748            // CAS returns the old value as a heap pointer
1749            builder.declare_value_needs_stack_map(old);
1750            Ok(SsaVal::HeapPtr(old))
1751        }
1752
1753        // ---------------------------------------------------------------
1754        // Bit operations \u2014 popCount, ctz
1755        // ---------------------------------------------------------------
1756        PrimOpKind::PopCnt | PrimOpKind::PopCnt64 => {
1757            check_arity(op, 1, args.len())?;
1758            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1759            Ok(SsaVal::Raw(builder.ins().popcnt(a), LIT_TAG_WORD))
1760        }
1761        PrimOpKind::PopCnt8 => {
1762            check_arity(op, 1, args.len())?;
1763            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1764            let masked = builder.ins().band_imm(a, 0xFF);
1765            Ok(SsaVal::Raw(builder.ins().popcnt(masked), LIT_TAG_WORD))
1766        }
1767        PrimOpKind::PopCnt16 => {
1768            check_arity(op, 1, args.len())?;
1769            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1770            let masked = builder.ins().band_imm(a, 0xFFFF);
1771            Ok(SsaVal::Raw(builder.ins().popcnt(masked), LIT_TAG_WORD))
1772        }
1773        PrimOpKind::PopCnt32 => {
1774            check_arity(op, 1, args.len())?;
1775            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1776            let masked = builder.ins().band_imm(a, 0xFFFF_FFFF);
1777            Ok(SsaVal::Raw(builder.ins().popcnt(masked), LIT_TAG_WORD))
1778        }
1779        PrimOpKind::Ctz | PrimOpKind::Ctz64 => {
1780            check_arity(op, 1, args.len())?;
1781            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1782            Ok(SsaVal::Raw(builder.ins().ctz(a), LIT_TAG_WORD))
1783        }
1784        PrimOpKind::Ctz8 => {
1785            check_arity(op, 1, args.len())?;
1786            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1787            // Set bit 8 so ctz stops there if all lower 8 bits are zero
1788            let with_sentinel = builder.ins().bor_imm(a, 0x100);
1789            Ok(SsaVal::Raw(builder.ins().ctz(with_sentinel), LIT_TAG_WORD))
1790        }
1791        PrimOpKind::Ctz16 => {
1792            check_arity(op, 1, args.len())?;
1793            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1794            let with_sentinel = builder.ins().bor_imm(a, 0x10000);
1795            Ok(SsaVal::Raw(builder.ins().ctz(with_sentinel), LIT_TAG_WORD))
1796        }
1797        PrimOpKind::Ctz32 => {
1798            check_arity(op, 1, args.len())?;
1799            let a = unbox_int(sess.pipeline, builder, sess.vmctx, args[0]);
1800            let with_sentinel = builder.ins().bor_imm(a, 0x1_0000_0000);
1801            Ok(SsaVal::Raw(builder.ins().ctz(with_sentinel), LIT_TAG_WORD))
1802        }
1803    }
1804}
1805
1806fn check_arity(op: &PrimOpKind, expected: usize, got: usize) -> Result<(), EmitError> {
1807    if expected != got {
1808        Err(EmitError::InvalidArity(*op, expected, got))
1809    } else {
1810        Ok(())
1811    }
1812}
1813
1814fn emit_int_compare(
1815    pipeline: &mut CodegenPipeline,
1816    builder: &mut FunctionBuilder,
1817    vmctx: Value,
1818    op: &PrimOpKind,
1819    cc: IntCC,
1820    args: &[SsaVal],
1821    tag: i64,
1822) -> Result<SsaVal, EmitError> {
1823    check_arity(op, 2, args.len())?;
1824    let a = unbox_int(pipeline, builder, vmctx, args[0]);
1825    let b = unbox_int(pipeline, builder, vmctx, args[1]);
1826    let cmp = builder.ins().icmp(cc, a, b);
1827    Ok(SsaVal::Raw(builder.ins().uextend(types::I64, cmp), tag))
1828}
1829
1830fn emit_double_compare(
1831    pipeline: &mut CodegenPipeline,
1832    builder: &mut FunctionBuilder,
1833    vmctx: Value,
1834    op: &PrimOpKind,
1835    cc: FloatCC,
1836    args: &[SsaVal],
1837    tag: i64,
1838) -> Result<SsaVal, EmitError> {
1839    check_arity(op, 2, args.len())?;
1840    let a = unbox_double(pipeline, builder, vmctx, args[0]);
1841    let b = unbox_double(pipeline, builder, vmctx, args[1]);
1842    let cmp = builder.ins().fcmp(cc, a, b);
1843    Ok(SsaVal::Raw(builder.ins().uextend(types::I64, cmp), tag))
1844}
1845
1846fn emit_f32_compare(
1847    pipeline: &mut CodegenPipeline,
1848    builder: &mut FunctionBuilder,
1849    vmctx: Value,
1850    op: &PrimOpKind,
1851    cc: FloatCC,
1852    args: &[SsaVal],
1853    tag: i64,
1854) -> Result<SsaVal, EmitError> {
1855    check_arity(op, 2, args.len())?;
1856    let a = unbox_float(pipeline, builder, vmctx, args[0]);
1857    let b = unbox_float(pipeline, builder, vmctx, args[1]);
1858    let cmp = builder.ins().fcmp(cc, a, b);
1859    Ok(SsaVal::Raw(builder.ins().uextend(types::I64, cmp), tag))
1860}
1861
1862/// Unbox an Addr# value recursively.
1863fn unbox_addr(
1864    _pipeline: &mut CodegenPipeline,
1865    builder: &mut FunctionBuilder,
1866    val: SsaVal,
1867) -> Value {
1868    match val {
1869        SsaVal::Raw(v, _) => v,
1870        SsaVal::HeapPtr(v) => {
1871            let start_block = builder.create_block();
1872            let next_block = builder.create_block();
1873            builder.append_block_param(start_block, types::I64);
1874            builder.append_block_param(next_block, types::I64);
1875
1876            builder.ins().jump(start_block, &[BlockArg::Value(v)]);
1877
1878            builder.switch_to_block(start_block);
1879            let curr_v = builder.block_params(start_block)[0];
1880            let tag = builder
1881                .ins()
1882                .load(types::I8, MemFlags::trusted(), curr_v, 0);
1883            let is_con = builder
1884                .ins()
1885                .icmp_imm(IntCC::Equal, tag, layout::TAG_CON as i64);
1886
1887            let con_block = builder.create_block();
1888            builder.ins().brif(
1889                is_con,
1890                con_block,
1891                &[],
1892                next_block,
1893                &[BlockArg::Value(curr_v)],
1894            );
1895
1896            builder.switch_to_block(con_block);
1897            builder.seal_block(con_block);
1898            let field0 = builder.ins().load(
1899                types::I64,
1900                MemFlags::trusted(),
1901                curr_v,
1902                layout::CON_FIELDS_OFFSET as i32,
1903            );
1904            builder.ins().jump(start_block, &[BlockArg::Value(field0)]);
1905
1906            builder.switch_to_block(next_block);
1907            builder.seal_block(start_block);
1908            builder.seal_block(next_block);
1909            let v_final = builder.block_params(next_block)[0];
1910
1911            let raw_val =
1912                builder
1913                    .ins()
1914                    .load(types::I64, MemFlags::trusted(), v_final, LIT_VALUE_OFFSET);
1915            let lit_tag =
1916                builder
1917                    .ins()
1918                    .load(types::I8, MemFlags::trusted(), v_final, LIT_TAG_OFFSET);
1919            let lit_tag_ext = builder.ins().uextend(types::I64, lit_tag);
1920
1921            let is_string = builder
1922                .ins()
1923                .icmp_imm(IntCC::Equal, lit_tag_ext, LIT_TAG_STRING);
1924            let is_ba = builder
1925                .ins()
1926                .icmp_imm(IntCC::Equal, lit_tag_ext, LIT_TAG_BYTEARRAY);
1927            let needs_adj = builder.ins().bor(is_string, is_ba);
1928            let adjusted = builder.ins().iadd_imm(raw_val, 8);
1929            builder.ins().select(needs_adj, adjusted, raw_val)
1930        }
1931    }
1932}
1933
1934/// Extract the raw ByteArray pointer from a Lit(BYTEARRAY) heap object recursively.
1935fn unbox_bytearray(
1936    _pipeline: &mut CodegenPipeline,
1937    builder: &mut FunctionBuilder,
1938    val: SsaVal,
1939) -> Value {
1940    match val {
1941        SsaVal::Raw(v, _) => v,
1942        SsaVal::HeapPtr(v) => {
1943            let start_block = builder.create_block();
1944            let next_block = builder.create_block();
1945            builder.append_block_param(start_block, types::I64);
1946            builder.append_block_param(next_block, types::I64);
1947
1948            builder.ins().jump(start_block, &[BlockArg::Value(v)]);
1949
1950            builder.switch_to_block(start_block);
1951            let curr_v = builder.block_params(start_block)[0];
1952            let tag = builder
1953                .ins()
1954                .load(types::I8, MemFlags::trusted(), curr_v, 0);
1955            let is_con = builder
1956                .ins()
1957                .icmp_imm(IntCC::Equal, tag, layout::TAG_CON as i64);
1958
1959            let con_block = builder.create_block();
1960            builder.ins().brif(
1961                is_con,
1962                con_block,
1963                &[],
1964                next_block,
1965                &[BlockArg::Value(curr_v)],
1966            );
1967
1968            builder.switch_to_block(con_block);
1969            builder.seal_block(con_block);
1970            let field0 = builder.ins().load(
1971                types::I64,
1972                MemFlags::trusted(),
1973                curr_v,
1974                layout::CON_FIELDS_OFFSET as i32,
1975            );
1976            builder.ins().jump(start_block, &[BlockArg::Value(field0)]);
1977
1978            builder.switch_to_block(next_block);
1979            builder.seal_block(start_block);
1980            builder.seal_block(next_block);
1981            let v_final = builder.block_params(next_block)[0];
1982
1983            let raw_val =
1984                builder
1985                    .ins()
1986                    .load(types::I64, MemFlags::trusted(), v_final, LIT_VALUE_OFFSET);
1987            let lit_tag =
1988                builder
1989                    .ins()
1990                    .load(types::I8, MemFlags::trusted(), v_final, LIT_TAG_OFFSET);
1991            let lit_tag_ext = builder.ins().uextend(types::I64, lit_tag);
1992
1993            // ByteArray# should also adjust for LIT_TAG_STRING if passed one.
1994            let is_string = builder
1995                .ins()
1996                .icmp_imm(IntCC::Equal, lit_tag_ext, LIT_TAG_STRING);
1997            let adjusted = builder.ins().iadd_imm(raw_val, 8);
1998            builder.ins().select(is_string, adjusted, raw_val)
1999        }
2000    }
2001}
2002
2003/// Unbox a numeric literal from a heap object. Handles Raw passthrough,
2004/// Con wrapper recursion, and thunk forcing.
2005fn unbox_numeric(
2006    pipeline: &mut CodegenPipeline,
2007    builder: &mut FunctionBuilder,
2008    vmctx: Value,
2009    val: SsaVal,
2010    load_type: types::Type,
2011) -> Value {
2012    match val {
2013        SsaVal::Raw(v, _) => v,
2014        SsaVal::HeapPtr(v) => {
2015            let start_block = builder.create_block();
2016            let next_block = builder.create_block();
2017            builder.append_block_param(start_block, types::I64);
2018            builder.append_block_param(next_block, types::I64);
2019
2020            builder.ins().jump(start_block, &[BlockArg::Value(v)]);
2021
2022            builder.switch_to_block(start_block);
2023            let curr_v = builder.block_params(start_block)[0];
2024            let tag = builder
2025                .ins()
2026                .load(types::I8, MemFlags::trusted(), curr_v, 0);
2027            let is_con = builder
2028                .ins()
2029                .icmp_imm(IntCC::Equal, tag, layout::TAG_CON as i64);
2030
2031            let con_block = builder.create_block();
2032            let check_thunk_block = builder.create_block();
2033            builder
2034                .ins()
2035                .brif(is_con, con_block, &[], check_thunk_block, &[]);
2036
2037            builder.switch_to_block(con_block);
2038            builder.seal_block(con_block);
2039            let field0 = builder.ins().load(
2040                types::I64,
2041                MemFlags::trusted(),
2042                curr_v,
2043                layout::CON_FIELDS_OFFSET as i32,
2044            );
2045            builder.ins().jump(start_block, &[BlockArg::Value(field0)]);
2046
2047            // Defense in depth: force thunks instead of trapping
2048            builder.switch_to_block(check_thunk_block);
2049            builder.seal_block(check_thunk_block);
2050            let is_thunk = builder
2051                .ins()
2052                .icmp_imm(IntCC::Equal, tag, layout::TAG_THUNK as i64);
2053            let thunk_force_block = builder.create_block();
2054            builder.ins().brif(
2055                is_thunk,
2056                thunk_force_block,
2057                &[],
2058                next_block,
2059                &[BlockArg::Value(curr_v)],
2060            );
2061            builder.switch_to_block(thunk_force_block);
2062            builder.seal_block(thunk_force_block);
2063            let force_fn = pipeline
2064                .module
2065                .declare_function("heap_force", Linkage::Import, &{
2066                    let mut sig = Signature::new(pipeline.isa.default_call_conv());
2067                    sig.params.push(AbiParam::new(types::I64)); // vmctx
2068                    sig.params.push(AbiParam::new(types::I64)); // obj
2069                    sig.returns.push(AbiParam::new(types::I64)); // forced
2070                    sig
2071                })
2072                .expect("declare heap_force");
2073            let force_ref = pipeline.module.declare_func_in_func(force_fn, builder.func);
2074            let inst = builder.ins().call(force_ref, &[vmctx, curr_v]);
2075            let forced = builder.inst_results(inst)[0];
2076            builder.ins().jump(start_block, &[BlockArg::Value(forced)]);
2077
2078            builder.switch_to_block(next_block);
2079            builder.seal_block(start_block);
2080            builder.seal_block(next_block);
2081            let v_final = builder.block_params(next_block)[0];
2082
2083            builder
2084                .ins()
2085                .load(load_type, MemFlags::trusted(), v_final, LIT_VALUE_OFFSET)
2086        }
2087    }
2088}
2089
2090pub fn unbox_int(
2091    pipeline: &mut CodegenPipeline,
2092    builder: &mut FunctionBuilder,
2093    vmctx: Value,
2094    val: SsaVal,
2095) -> Value {
2096    unbox_numeric(pipeline, builder, vmctx, val, types::I64)
2097}
2098
2099pub fn unbox_double(
2100    pipeline: &mut CodegenPipeline,
2101    builder: &mut FunctionBuilder,
2102    vmctx: Value,
2103    val: SsaVal,
2104) -> Value {
2105    unbox_numeric(pipeline, builder, vmctx, val, types::F64)
2106}
2107
2108pub fn unbox_float(
2109    pipeline: &mut CodegenPipeline,
2110    builder: &mut FunctionBuilder,
2111    vmctx: Value,
2112    val: SsaVal,
2113) -> Value {
2114    unbox_numeric(pipeline, builder, vmctx, val, types::F32)
2115}
2116
2117/// Allocate a Lit heap object with LIT_TAG_BYTEARRAY, storing a raw pointer.
2118fn emit_lit_bytearray(
2119    builder: &mut FunctionBuilder,
2120    vmctx: Value,
2121    gc_sig: ir::SigRef,
2122    oom_func: ir::FuncRef,
2123    ba_ptr: Value,
2124) -> SsaVal {
2125    let ptr = emit_alloc_fast_path(builder, vmctx, LIT_TOTAL_SIZE, gc_sig, oom_func);
2126    let tag = builder.ins().iconst(types::I8, layout::TAG_LIT as i64);
2127    builder.ins().store(MemFlags::trusted(), tag, ptr, 0);
2128    let size = builder.ins().iconst(types::I16, LIT_TOTAL_SIZE as i64);
2129    builder.ins().store(MemFlags::trusted(), size, ptr, 1);
2130    let lit_tag = builder.ins().iconst(types::I8, LIT_TAG_BYTEARRAY);
2131    builder
2132        .ins()
2133        .store(MemFlags::trusted(), lit_tag, ptr, LIT_TAG_OFFSET);
2134    builder
2135        .ins()
2136        .store(MemFlags::trusted(), ba_ptr, ptr, LIT_VALUE_OFFSET);
2137    builder.declare_value_needs_stack_map(ptr);
2138    SsaVal::HeapPtr(ptr)
2139}
2140
2141/// Allocate a Lit heap object for a boxed array (SmallArray# or Array#).
2142fn emit_lit_boxed_array(
2143    builder: &mut FunctionBuilder,
2144    vmctx: Value,
2145    gc_sig: ir::SigRef,
2146    oom_func: ir::FuncRef,
2147    arr_ptr: Value,
2148    lit_tag: i64,
2149) -> SsaVal {
2150    let ptr = emit_alloc_fast_path(builder, vmctx, LIT_TOTAL_SIZE, gc_sig, oom_func);
2151    let tag = builder.ins().iconst(types::I8, layout::TAG_LIT as i64);
2152    builder.ins().store(MemFlags::trusted(), tag, ptr, 0);
2153    let size = builder.ins().iconst(types::I16, LIT_TOTAL_SIZE as i64);
2154    builder.ins().store(MemFlags::trusted(), size, ptr, 1);
2155    let lt = builder.ins().iconst(types::I8, lit_tag);
2156    builder
2157        .ins()
2158        .store(MemFlags::trusted(), lt, ptr, LIT_TAG_OFFSET);
2159    builder
2160        .ins()
2161        .store(MemFlags::trusted(), arr_ptr, ptr, LIT_VALUE_OFFSET);
2162    builder.declare_value_needs_stack_map(ptr);
2163    SsaVal::HeapPtr(ptr)
2164}
2165
2166/// Call a runtime function by name. Returns the result value (or a dummy if no returns).
2167fn emit_runtime_call(
2168    pipeline: &mut CodegenPipeline,
2169    builder: &mut FunctionBuilder,
2170    name: &str,
2171    params: &[AbiParam],
2172    returns: &[AbiParam],
2173    arg_vals: &[Value],
2174) -> Result<Value, EmitError> {
2175    let mut sig = Signature::new(pipeline.isa.default_call_conv());
2176    for p in params {
2177        sig.params.push(*p);
2178    }
2179    for r in returns {
2180        sig.returns.push(*r);
2181    }
2182    let func_id = pipeline
2183        .module
2184        .declare_function(name, Linkage::Import, &sig)
2185        .map_err(|e| EmitError::CraneliftError(e.to_string()))?;
2186    let func_ref = pipeline.module.declare_func_in_func(func_id, builder.func);
2187    let inst = builder.ins().call(func_ref, arg_vals);
2188    if returns.is_empty() {
2189        Ok(builder.ins().iconst(types::I64, 0))
2190    } else {
2191        Ok(builder.inst_results(inst)[0])
2192    }
2193}