Skip to main content

rapx/verify/call_summary/
builtin_models.rs

1//! Builtin call models: API behaviour modelling when MIR is unavailable.
2//!
3//! Each recognised standard-library API is described by a single table
4//! row: a **matcher** (from [`crate::verify::api_classify`]) and an
5//! **effect builder**.  [`lookup_effect`] scans the table linearly (first match
6//! wins) and converts the matched row into the effect summary consumed by the
7//! VM; [`is_modeled`] reports whether a call is in the table (used by the path
8//! graph to keep modelled calls opaque instead of inlining their branchy CFG).
9//!
10//! Two layers:
11//! 1. **Matchers** — [`crate::verify::api_classify`] `DefId` classifiers.
12//! 2. **Effect functions** — produce the `Vec<CallEffect>` for a single API.
13
14use rustc_hir::def_id::DefId;
15use rustc_middle::mir::Operand;
16use rustc_middle::ty::{Ty, TyCtxt};
17
18use super::from_raw_parts_elem_size;
19use super::{CallEffect, CallEffectSummary};
20use crate::helpers::mir_utils::{destination_stride, pointee_alignment, pointee_ty, type_layout};
21use crate::verify::api_classify;
22
23// ── Context for effect builders ────────────────────────────────────────
24
25pub(crate) struct EffCtx<'a, 'tcx> {
26    pub tcx: TyCtxt<'tcx>,
27    pub caller: DefId,
28    pub func: &'a Operand<'tcx>,
29    pub dest: Option<rustc_middle::mir::Local>,
30}
31
32// ── Registry table ─────────────────────────────────────────────────────
33
34struct Entry {
35    matches: fn(Option<DefId>) -> bool,
36    effects: fn(&EffCtx<'_, '_>) -> Vec<CallEffect>,
37}
38
39/// `DefId`-based matcher row (`fn(Option<DefId>) -> bool`).
40macro_rules! ED {
41    ($m:expr, $e:ident) => {
42        Entry {
43            matches: $m,
44            effects: $e,
45        }
46    };
47}
48
49static REGISTRY: &[Entry] = &[
50    // ── Intrinsics — no MIR body ────────────────────────────────────
51    // Compiler intrinsics (`core::intrinsics::*`) have no MIR to inline, so the
52    // hand-written effect is the *only* model. `mem::size_of`/`align_of` are
53    // `#[inline]` wrappers around the intrinsic and are matched for the same
54    // reason.
55    ED!(api_classify::is_layout_constant, eff_layout_const),
56    ED!(
57        api_classify::is_select_unpredictable,
58        eff_select_unpredictable
59    ),
60    // ── MIR unavailable — non-#[inline] cross-crate ─────────────────
61    // These std functions are not `#[inline]`, so precompiled std ships no MIR
62    // for them (`tcx.is_mir_available` is false) and the effect is the only
63    // model. (`Vec::push` itself is `#[inline]` but is grouped with
64    // `reserve`/`reserve_exact` — which are not — for the shared
65    // write-to-buffer model.)
66    ED!(api_classify::is_vec_alloc_constructor, eff_new_allocation),
67    ED!(api_classify::is_box_alloc_ctor, eff_box_alloc),
68    ED!(api_classify::is_exchange_malloc, eff_exchange_malloc),
69    // `into_vec` / `box_assume_init_into_vec_unsafe`: needed on older
70    // toolchains where `vec![…]` literals lower to `into_vec` (not `from_elem`).
71    ED!(api_classify::is_vec_from_box, eff_vec_from_box),
72    // `slice::to_vec` allocates a fresh buffer and copies the slice — model it
73    // like `into_vec`/`box_assume_init_into_vec_unsafe` (fresh allocation, len
74    // == cap), so `String::from(&str)`'s internal Vec buffer gets provenance.
75    ED!(api_classify::is_slice_to_vec, eff_vec_from_box),
76    ED!(
77        api_classify::is_vec_with_capacity,
78        eff_new_allocation_from_cap
79    ),
80    ED!(api_classify::is_into_boxed_slice, eff_box_from_vec),
81    ED!(api_classify::is_vec_push_or_reserve, eff_write_mem),
82    // ── MIR available — precise symbolic summary ────────────────────
83    // The following have MIR available (`#[inline]` or local), but a symbolic
84    // summary is more precise than inlining their branchy / bit-twiddling
85    // bodies.
86
87    // Non-zero-preserving integer ops are modelled as a precise expression over
88    // their operands (ite / arithmetic) so a downstream `!= 0` obligation
89    // discharges *conditionally* — only when the operands are actually
90    // non-zero — rather than unconditionally.
91    ED!(api_classify::is_max, eff_return_max),
92    ED!(api_classify::is_clamp, eff_return_clamp),
93    ED!(api_classify::is_abs, eff_return_abs),
94    ED!(api_classify::is_neg, eff_return_neg),
95    ED!(api_classify::is_sat_unchecked_add, eff_return_add),
96    ED!(api_classify::is_sat_unchecked_mul, eff_return_mul),
97    ED!(api_classify::is_checked_add, eff_return_option_some_add),
98    ED!(api_classify::is_checked_mul, eff_return_option_some_mul),
99    ED!(api_classify::is_overflowing_abs_neg, eff_overflowing_nz),
100    ED!(api_classify::is_unwrap, eff_alias_arg0),
101    // Pointer arithmetic: direction and granularity are orthogonal; each entry
102    // picks a fixed-stride `ReturnPointerAdd`/`ReturnPointerSub`.
103    ED!(api_classify::is_element_ptr_add, eff_ptr_add),
104    ED!(api_classify::is_element_ptr_sub, eff_ptr_sub),
105    ED!(api_classify::is_byte_ptr_add, eff_ptr_add_byte),
106    ED!(api_classify::is_byte_ptr_sub, eff_ptr_sub_byte),
107    // Pointer extraction / cast: `as_ptr`/`into_raw` return a non-null, aligned,
108    // initialized alias of their argument (`ReturnPointerFromArg` derives those
109    // from the source). Raw-pointer `cast` is excluded (`is_as_ptr_valid`)
110    // because it only reinterprets the address and is left to MIR inlining.
111    ED!(api_classify::is_as_ptr_valid, eff_alias_ptr),
112    // `str::as_bytes` reinterprets `&str` as `&[u8]` (same data pointer + len).
113    ED!(api_classify::is_str_as_bytes, eff_alias_arg0),
114    // Slice / collection queries.
115    ED!(api_classify::is_len, eff_len),
116    ED!(api_classify::is_capacity, eff_len),
117    ED!(api_classify::is_min_like, eff_cmp_min),
118    ED!(api_classify::is_bit_preserving_nz, eff_return_nonzero_iff),
119    ED!(
120        api_classify::is_checked_nonzero_iff,
121        eff_return_option_some_nonzero_iff
122    ),
123    ED!(
124        api_classify::is_checked_next_pow2,
125        eff_return_option_some_nonzero
126    ),
127    // SliceIndex::get_unchecked / get_unchecked_mut.
128    ED!(api_classify::is_slice_get_unchecked, eff_alias_ptr),
129    // `SliceIndex::get_unchecked(self, slice)` returns `slice + self` (the
130    // receiver is the *index*, the slice pointer is argument 1), so model it as
131    // element-strided pointer arithmetic off argument 1 rather than an alias of
132    // argument 0.
133    ED!(
134        api_classify::is_sliceindex_get_unchecked,
135        eff_sliceindex_get_unchecked
136    ),
137    // Ownership reconstruction (`Box::from_raw` / `CString::from_raw` / …).
138    ED!(
139        api_classify::is_ownership_reconstruction,
140        eff_ownership_recon
141    ),
142    // `ManuallyDrop::drop` releases the pointee's heap allocation.
143    ED!(api_classify::is_manually_drop_drop, eff_drop_memory),
144    // `std::mem::drop` frees the argument's heap allocation.
145    ED!(api_classify::is_std_drop, eff_drop_memory),
146    // `drop_in_place::<T>` drops the pointee in place, freeing its heap
147    // allocation (e.g. a `Box`/`Vec` buffer) for the alias/owning checkers.
148    ED!(api_classify::is_drop_in_place, eff_drop_memory),
149    // Slice helpers.
150    ED!(api_classify::is_split_at, eff_split_at),
151    ED!(api_classify::is_from_raw_parts, eff_from_raw_parts),
152    ED!(api_classify::is_align_offset, eff_align_offset),
153    ED!(api_classify::is_cstr_from_ptr, eff_alias_arg0),
154    ED!(api_classify::is_slice_range, eff_slice_range),
155    ED!(api_classify::is_mem_replace, eff_mem_replace),
156    // Layout accessor (`align_of`): returns a power of two.
157    ED!(api_classify::is_layout_align, eff_layout_align),
158    // Local re-implementations (std-challenge suites' `_ext` fns): matched by
159    // name-scanning `fn_defs()`. Their MIR is local, but the re-implemented
160    // algorithm is modelled with the same effect as the std original.
161    // (`iter`/`into_iter`/`iter_mut` are *not* here — they are derived from
162    // the return type by `try_iter_constructor_effect`.)
163    ED!(api_classify::is_align_to_local, eff_align_to),
164    ED!(api_classify::is_iter_position, eff_option_scan_index),
165    ED!(api_classify::is_strlen, eff_scan_length),
166    // ── MIR available — deliberately opaque ─────────────────────────
167    // `eff_none` stubs: no symbolic effect, but staying registered keeps
168    // `is_modeled` true so the path graph does not inline their branchy bodies
169    // (`NonNull::new`'s `is_null`, `MaybeUninit::uninit`/`assume_init`).
170    // `MaybeUninit::write` marks the slot initialized (`WriteMemory`).
171    ED!(api_classify::is_nonnull_checked_new, eff_none),
172    ED!(api_classify::is_nonnull_new_unchecked, eff_new_unchecked),
173    ED!(api_classify::is_maybe_uninit_uninit, eff_none),
174    ED!(api_classify::is_maybe_uninit_assume_init, eff_none),
175    ED!(api_classify::is_maybe_uninit_write, eff_write_mem),
176];
177
178/// True when `callee` matches a hand-modelled API in the registry. The path
179/// graph uses this to keep such calls opaque (it must not inline their branchy
180/// CFG when the VM models their semantics more precisely).
181pub(crate) fn is_modeled(callee: Option<DefId>) -> bool {
182    REGISTRY.iter().any(|e| (e.matches)(callee))
183}
184
185pub(crate) fn lookup_effect<'tcx>(
186    tcx: TyCtxt<'tcx>,
187    caller: DefId,
188    callee: Option<DefId>,
189    func: &Operand<'tcx>,
190    destination: rustc_middle::mir::Local,
191) -> Option<CallEffectSummary> {
192    let dest = Some(destination);
193    for e in REGISTRY {
194        if (e.matches)(callee) {
195            let ctx = EffCtx {
196                tcx,
197                caller,
198                func,
199                dest,
200            };
201            return Some(CallEffectSummary {
202                effects: (e.effects)(&ctx),
203                unsupported: false,
204            });
205        }
206    }
207    None
208}
209
210// ── Effect builders — one small function per API semantic ──────────────
211
212fn eff_none(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
213    Vec::new()
214}
215
216fn eff_select_unpredictable(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
217    vec![CallEffect::SelectUnpredictable]
218}
219
220fn eff_alias_ptr(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
221    let mut eff = vec![CallEffect::ReturnPointerFromArg { arg: 0 }];
222    if pointee_alignment(ctx.tcx, ctx.caller, ctx.dest).is_some() {
223        eff.push(CallEffect::ReturnAligned);
224    }
225    eff
226}
227
228/// `SliceIndex::get_unchecked(self, slice)` / `get_unchecked_mut`: returns an
229/// element pointer at `slice + self` (receiver is the index, slice pointer is
230/// argument 1). Element-strided add off argument 1, inheriting non-nullness
231/// from the slice.
232fn eff_sliceindex_get_unchecked(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
233    if !dest_is_pointer(ctx.tcx, ctx.caller, ctx.dest) {
234        return Vec::new();
235    }
236    vec![CallEffect::ReturnPointerAdd {
237        base_arg: 1,
238        offset_arg: 0,
239        stride: destination_stride(ctx.tcx, ctx.caller, ctx.dest),
240        dereferenceable: true,
241    }]
242}
243
244fn eff_alias_arg0(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
245    vec![CallEffect::ReturnAliasArg { arg: 0 }]
246}
247
248/// `NonNull::new_unchecked(ptr)`: a transparent re-wrap that preserves `ptr`'s
249/// value and provenance (element offset included), inheriting non-nullness from
250/// the source rather than asserting it.
251fn eff_new_unchecked(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
252    vec![CallEffect::ReturnPointerFromArg { arg: 0 }]
253}
254
255fn eff_ptr_add(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
256    eff_ptr_arith(ctx, PtrDirection::Add, PtrGranularity::Element)
257}
258
259fn eff_ptr_sub(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
260    eff_ptr_arith(ctx, PtrDirection::Sub, PtrGranularity::Element)
261}
262
263fn eff_ptr_add_byte(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
264    eff_ptr_arith(ctx, PtrDirection::Add, PtrGranularity::Byte)
265}
266
267fn eff_ptr_sub_byte(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
268    eff_ptr_arith(ctx, PtrDirection::Sub, PtrGranularity::Byte)
269}
270
271enum PtrDirection {
272    Add,
273    Sub,
274}
275
276enum PtrGranularity {
277    Element,
278    Byte,
279}
280
281/// Shared model for `ReturnPointerAdd`/`ReturnPointerSub`.
282///
283/// `wrapping_add`/`wrapping_sub` are shared between integers and raw pointers.
284/// When the destination is not a pointer type the call is an integer
285/// `wrapping_add`, whose result may wrap to zero, so it is left unconstrained
286/// rather than modelled as pointer arithmetic.
287fn eff_ptr_arith(
288    ctx: &EffCtx<'_, '_>,
289    dir: PtrDirection,
290    granularity: PtrGranularity,
291) -> Vec<CallEffect> {
292    if !dest_is_pointer(ctx.tcx, ctx.caller, ctx.dest) {
293        return Vec::new();
294    }
295    let stride = match granularity {
296        PtrGranularity::Byte => Some(1),
297        PtrGranularity::Element => destination_stride(ctx.tcx, ctx.caller, ctx.dest),
298    };
299    let effect = match dir {
300        PtrDirection::Sub => CallEffect::ReturnPointerSub {
301            base_arg: 0,
302            offset_arg: 1,
303            stride,
304        },
305        PtrDirection::Add => CallEffect::ReturnPointerAdd {
306            base_arg: 0,
307            offset_arg: 1,
308            stride,
309            dereferenceable: false,
310        },
311    };
312    vec![effect]
313}
314
315fn eff_write_mem(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
316    vec![CallEffect::WriteMemory { pointer_arg: 0 }]
317}
318
319fn eff_len(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
320    vec![CallEffect::ReturnLengthOfArg { arg: 0 }]
321}
322
323fn eff_cmp_min(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
324    vec![CallEffect::ReturnMin {
325        lhs_arg: 0,
326        rhs_arg: 1,
327    }]
328}
329
330fn eff_return_nonzero_iff(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
331    vec![CallEffect::ReturnNonZeroIff { arg: 0 }]
332}
333
334fn eff_return_option_some_nonzero_iff(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
335    vec![CallEffect::ReturnOptionSomeNonZeroIff { arg: 0 }]
336}
337
338fn eff_return_option_some_nonzero(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
339    vec![CallEffect::ReturnOptionSomeNonZero]
340}
341
342fn eff_return_max(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
343    vec![CallEffect::ReturnMax {
344        lhs_arg: 0,
345        rhs_arg: 1,
346    }]
347}
348
349fn eff_return_clamp(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
350    vec![CallEffect::ReturnClamp {
351        value_arg: 0,
352        min_arg: 1,
353        max_arg: 2,
354    }]
355}
356
357fn eff_return_abs(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
358    vec![CallEffect::ReturnAbs { arg: 0 }]
359}
360
361fn eff_return_neg(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
362    vec![CallEffect::ReturnNeg { arg: 0 }]
363}
364
365fn eff_return_add(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
366    vec![CallEffect::ReturnAdd {
367        lhs_arg: 0,
368        rhs_arg: 1,
369    }]
370}
371
372fn eff_return_mul(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
373    vec![CallEffect::ReturnMul {
374        lhs_arg: 0,
375        rhs_arg: 1,
376    }]
377}
378
379fn eff_return_option_some_add(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
380    vec![CallEffect::ReturnOptionSomeAdd {
381        lhs_arg: 0,
382        rhs_arg: 1,
383    }]
384}
385
386fn eff_return_option_some_mul(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
387    vec![CallEffect::ReturnOptionSomeMul {
388        lhs_arg: 0,
389        rhs_arg: 1,
390    }]
391}
392
393fn eff_overflowing_nz(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
394    vec![CallEffect::ReturnTupleFieldNonZero { field: 0 }]
395}
396
397fn eff_ownership_recon(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
398    vec![
399        CallEffect::ReturnAliasArg { arg: 0 },
400        CallEffect::ReturnNonZero,
401        CallEffect::OwnsInitMemory { arg: 0 },
402    ]
403}
404
405fn eff_drop_memory(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
406    vec![CallEffect::DropMemory { pointer_arg: 0 }]
407}
408
409fn eff_align_to(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
410    vec![CallEffect::ReturnAlignTo { receiver_arg: 0 }]
411}
412
413fn eff_option_scan_index(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
414    vec![CallEffect::ReturnOptionSomeScanIndex { self_arg: 0 }]
415}
416
417fn eff_scan_length(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
418    vec![CallEffect::ReturnScanLength]
419}
420
421fn eff_align_offset(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
422    vec![CallEffect::ReturnAlignOffset {
423        ptr_arg: 0,
424        align_arg: 1,
425    }]
426}
427
428fn eff_split_at(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
429    vec![
430        CallEffect::ReturnAliasArg { arg: 0 },
431        CallEffect::ReturnTupleFieldLength {
432            field: 0,
433            from_arg: 1,
434        },
435    ]
436}
437
438fn eff_slice_range(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
439    vec![CallEffect::ReturnRange { bounds_arg: 1 }]
440}
441
442fn eff_mem_replace(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
443    vec![CallEffect::ReturnDerefArg { arg: 0 }]
444}
445
446fn eff_from_raw_parts(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
447    let elem = from_raw_parts_elem_size(ctx.tcx, ctx.caller, ctx.dest);
448    let mut eff = vec![
449        // ReturnAliasArg keeps the legacy PointsTo chain intact so the
450        // legacy SMT Align checker can trace through as_ptr() → reference
451        // provenance.  Without it, place_is_reference_aligned cannot
452        // prove alignment for the pointer argument.
453        CallEffect::ReturnAliasArg { arg: 0 },
454        // ReturnFreshAllocation provides the allocation-tracking hint
455        // used by the VM backend's memory model.
456        CallEffect::ReturnFreshAllocation {
457            pointer_arg: 0,
458            size_arg: 1,
459            elem_size: elem,
460        },
461        CallEffect::ReturnNonZero,
462    ];
463    if pointee_alignment(ctx.tcx, ctx.caller, ctx.dest).is_some() {
464        eff.push(CallEffect::ReturnAligned);
465    }
466    eff
467}
468
469fn eff_new_allocation(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
470    let elem = from_raw_parts_elem_size(ctx.tcx, ctx.caller, ctx.dest);
471    vec![CallEffect::ReturnNewAllocation {
472        size_arg: 1,
473        elem_size: elem,
474    }]
475}
476
477fn eff_box_alloc(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
478    vec![CallEffect::ReturnBoxAllocation]
479}
480
481fn eff_exchange_malloc(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
482    vec![CallEffect::ReturnExchangeMalloc { size_arg: 0 }]
483}
484
485fn eff_new_allocation_from_cap(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
486    let elem = from_raw_parts_elem_size(ctx.tcx, ctx.caller, ctx.dest);
487    vec![CallEffect::ReturnNewAllocationFromCap {
488        cap_arg: 0,
489        elem_size: elem,
490    }]
491}
492
493fn eff_vec_from_box(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
494    vec![CallEffect::ReturnNewAllocationFromBox]
495}
496
497fn eff_layout_align(_ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
498    vec![CallEffect::ReturnPowerOfTwo]
499}
500
501fn eff_layout_const(ctx: &EffCtx<'_, '_>) -> Vec<CallEffect> {
502    layout_constant_effect(ctx.tcx, ctx.caller, ctx.func)
503        .into_iter()
504        .collect()
505}
506
507// ── Layout helpers (used by effect builders) ─────────────────────────
508
509fn dest_is_pointer(tcx: TyCtxt<'_>, caller: DefId, dest: Option<rustc_middle::mir::Local>) -> bool {
510    let Some(d) = dest else { return false };
511    pointee_ty(tcx.optimized_mir(caller).local_decls[d].ty).is_some()
512}
513
514fn layout_call_ty<'tcx>(func: &Operand<'tcx>) -> Option<Ty<'tcx>> {
515    crate::helpers::mir_utils::fn_def_first_type_arg(func)
516}
517
518fn layout_constant_effect<'tcx>(
519    tcx: TyCtxt<'tcx>,
520    caller: DefId,
521    func: &Operand<'tcx>,
522) -> Option<CallEffect> {
523    let ty = layout_call_ty(func)?;
524    let (align, size) = type_layout(tcx, caller, ty)?;
525    // `type_layout` reports `(0, 0)` for a generic `T` (layout unknown).  Leave
526    // that case to the VM's `try_size_align_effect`, which binds the shared
527    // symbolic `sizeof_T` / `align_T` (and keeps `align_of::<T>() >= 1`, so a
528    // cast like `align as *const T` in `NonNull::dangling` is non-null).
529    if align == 0 && size == 0 {
530        return None;
531    }
532    let Some(callee) = crate::helpers::mir_utils::dep_callee_def_id(func) else {
533        return None;
534    };
535    if crate::def_id::contains(
536        &[
537            crate::def_id::mem_align_of(),
538            crate::def_id::intrinsics_align_of(),
539        ],
540        callee,
541    ) {
542        Some(CallEffect::ReturnConst { value: align })
543    } else if crate::def_id::contains(
544        &[
545            crate::def_id::mem_size_of(),
546            crate::def_id::intrinsics_size_of(),
547        ],
548        callee,
549    ) {
550        Some(CallEffect::ReturnConst { value: size })
551    } else {
552        None
553    }
554}
555
556fn eff_box_from_vec(_: &EffCtx<'_, '_>) -> Vec<CallEffect> {
557    vec![CallEffect::ReturnBoxFromVec { arg: 0 }]
558}