Skip to main content

qcode/value/
block_param.rs

1use crate::value::QCodeMut;
2use crate::{
3    context::Context,
4    error::Result,
5    types::TypeId,
6    value::{
7        LocalBlockId, LocalValueId, ModuleView, QCodeView, Value, ValueId,
8        block::{BlockId, BlockRef},
9        util::{
10            base_ref::{BaseRef, WithCtx, WithCtxMut},
11            named::{Named, Renameable},
12        },
13    },
14};
15use jstd::Identifier;
16use std::{
17    borrow::Cow,
18    fmt::{Display, Formatter},
19    marker::PhantomData,
20};
21
22/// Function-local block-parameter index (indexes the owning [`FunctionBody`](crate::value::FunctionBody)'s
23/// param arena).
24#[derive(Identifier)]
25pub struct LocalParamId(u32);
26
27crate::composite_id!(BlockParamId, LocalParamId);
28
29impl BlockParamId {
30    /// Qualified value form used directly as a Builder operand.
31    pub fn id(self) -> ValueId {
32        ValueId::BlockParam(self)
33    }
34}
35
36/// A typed parameter declared at the entry of a basic block.
37///
38/// Block parameters are the receiving side of block arguments: when a
39/// [`Branch`](crate::value::insn::Branch) or
40/// [`CBranch`](crate::value::insn::CBranch) passes arguments to a target
41/// block, the i-th argument binds to the i-th `BlockParam` of that block.
42///
43/// Unlike [`Instruction`](crate::value::Instruction) results, block params are
44/// not produced by any operation — they are value sources at block entry,
45/// analogous to function arguments in MLIR block-argument style.
46#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
47pub struct BlockParam<'str> {
48    /// Position of this param in the owning block's param list.
49    pub index: usize,
50
51    /// The type of this parameter's value.
52    pub type_id: TypeId,
53
54    /// The block this parameter belongs to. `pub(crate)` (in-crate struct
55    /// construction only): foreign crates read via [`BlockParam::parent_id`] and
56    /// write via [`BlockParam::set_parent`] (stage 6a §11 — full-private pends a
57    /// cross-module constructor).
58    pub(crate) parent: Option<LocalBlockId>,
59
60    /// Optional debug name (displayed as `%name`).
61    pub name: Option<Cow<'str, str>>,
62
63    /// Optional source value this param was created to promote (the varnode or
64    /// stack-slot literal). Not displayed; it is a stable cross-run identity that
65    /// lets passes like mem2reg reuse an existing param instead of duplicating it,
66    /// even for varnodes that have no `name`.
67    pub origin: Option<LocalValueId>,
68}
69
70impl<'str> BlockParam<'str> {
71    /// Allocates a new block parameter in `ctx`, attaches it to `block_id`, and
72    /// returns a mutable reference. The caller is responsible for appending the
73    /// returned `BlockParamId` to the block's `params` list.
74    pub fn make<'ctx>(
75        ctx: &'ctx mut Context<'str>,
76        block_id: BlockId,
77        size: usize,
78    ) -> BlockParamMutRef<'str, 'ctx> {
79        let type_id = ctx.shared.types.get_or_make_int(size);
80        let index = ctx.block(block_id).params.len();
81        let id = ctx.push_block_param(
82            block_id.func,
83            BlockParam {
84                index,
85                type_id,
86                parent: Some(block_id.local),
87                name: None,
88                origin: None,
89            },
90        );
91        BlockParamMutRef::from_id(ctx, id)
92    }
93
94    /// A detached, unnamed parameter of type `type_id` at position `index`,
95    /// attached to body-local `parent`. Public constructor so foreign crates need
96    /// not name the private `parent` field (stage 6a §11); the caller pushes the returned
97    /// value through [`Context::push_block_param`](crate::context::Context::push_block_param).
98    pub fn new(index: usize, type_id: TypeId, parent: LocalBlockId) -> Self {
99        Self {
100            index,
101            type_id,
102            parent: Some(parent),
103            name: None,
104            origin: None,
105        }
106    }
107
108    pub fn from_id<'ctx>(ctx: &'ctx Context<'str>, id: BlockParamId) -> BlockParamRef<'str, 'ctx> {
109        BlockParamRef::new(ModuleView::new(ctx), id)
110    }
111
112    pub fn from_id_mut<'ctx>(
113        ctx: &'ctx mut Context<'str>,
114        id: BlockParamId,
115    ) -> BlockParamMutRef<'str, 'ctx> {
116        BlockParamMutRef::from_id(ctx, id)
117    }
118
119    /// The block this parameter belongs to, if any (raw `&BlockParam` accessor).
120    /// Returns the body-local storage form; qualify it with the parameter's
121    /// function id at module/ref boundaries.
122    pub fn parent_id(&self) -> Option<LocalBlockId> {
123        self.parent
124    }
125
126    /// Attach this parameter to `block` (raw `&mut BlockParam` accessor).
127    pub fn set_parent(&mut self, block: LocalBlockId) {
128        self.parent = Some(block);
129    }
130
131    /// The source value this parameter was created to promote, if recorded (raw
132    /// `&BlockParam` accessor). Returns the body-local storage form; qualify it
133    /// with the parameter's function id at module/ref boundaries.
134    pub fn origin_id(&self) -> Option<LocalValueId> {
135        self.origin
136    }
137
138    /// Record the source value this parameter promotes (raw `&mut BlockParam`
139    /// accessor; see [`BlockParam::origin`]).
140    pub fn set_origin_id(&mut self, origin: LocalValueId) {
141        self.origin = Some(origin);
142    }
143}
144
145// Shared read-only methods available on both BlockParamRef and BlockParamMutRef
146impl<'s, 'ctx: 's, 'str: 'ctx, R> BlockParamRef<'str, 'ctx, R>
147where
148    R: QCodeView<'ctx, 'str>,
149{
150    fn inner(&'s self) -> &'ctx BlockParam<'str> {
151        self.view.block_param(self.id)
152    }
153
154    /// Position of this parameter in the owning block's param list.
155    pub fn index(&'s self) -> usize {
156        self.inner().index
157    }
158
159    /// The [`TypeId`] of this parameter's value.
160    pub fn type_id(&'s self) -> TypeId {
161        self.inner().type_id
162    }
163
164    /// Size of this parameter's value in bytes.
165    pub fn size(&'s self) -> usize {
166        self.view.shared().types.size_of(self.inner().type_id)
167    }
168
169    /// The block this parameter belongs to, if any.
170    pub fn parent(&'s self) -> Option<BlockRef<'str, 'ctx, R>> {
171        self.inner()
172            .parent
173            .map(|local| BlockRef::new(self.view, BlockId::new(self.id.func, local)))
174    }
175
176    pub fn name(&'s self) -> Option<&'ctx str> {
177        self.inner().name.as_deref()
178    }
179
180    /// The source value this param was created to promote, if recorded.
181    pub fn origin(&'s self) -> Option<ValueId> {
182        self.inner()
183            .origin
184            .map(|origin| origin.qualify(self.id.func))
185    }
186
187    fn fmt(&'s self, f: &mut Formatter<'_>) -> std::fmt::Result {
188        // Surface a richer-than-integer type (e.g. a seeded `TEB*` segment base)
189        // as a `Type ` prefix. Plain `Int` params stay bare `@name` so the many
190        // existing signature assertions (`<f @ESP @EDI>`) are unaffected.
191        let types = &self.view.shared().types;
192        let ty = types.type_name(self.type_id());
193        if types.pointee_of(self.type_id()).is_some()
194            || types.struct_name_of(self.type_id()).is_some()
195        {
196            write!(f, "{ty} ")?;
197        }
198        if let Some(name) = self.name() {
199            write!(f, "@{name}")
200        } else {
201            let id: usize = self.id.local.into();
202            write!(f, "@param{id:x}")
203        }
204    }
205
206    /// Formats this parameter in *declaration* position — the form that appears
207    /// in a block header, `@name:iN`. Unlike the operand [`fmt`](Self::fmt), a
208    /// scalar param surfaces its type as a `:iN`/`:fN` suffix so the header
209    /// round-trips through the parser's `block_param_decl` rule. Pointer/struct
210    /// params (no parser syntax) and unnamed/untyped params fall back to the
211    /// operand rendering.
212    pub(crate) fn fmt_decl(&'s self, f: &mut Formatter<'_>) -> std::fmt::Result {
213        let types = &self.view.shared().types;
214        let tid = self.type_id();
215        let is_scalar = types.pointee_of(tid).is_none() && types.struct_name_of(tid).is_none();
216        match (self.name(), is_scalar && self.size() > 0) {
217            (Some(name), true) => write!(f, "@{name}:{}", types.type_name(tid)),
218            _ => self.fmt(f),
219        }
220    }
221}
222
223#[derive(Clone, Copy)]
224pub struct BlockParamRef<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
225    pub id: BlockParamId,
226    pub(in crate::value) view: R,
227    marker: PhantomData<&'ctx &'str ()>,
228}
229
230impl<'str, 'ctx, R> BlockParamRef<'str, 'ctx, R> {
231    pub fn new(view: R, id: BlockParamId) -> Self {
232        Self {
233            id,
234            view,
235            marker: PhantomData,
236        }
237    }
238
239    pub fn id(&self) -> ValueId {
240        self.id.into()
241    }
242}
243
244impl<'str, 'ctx> BlockParamRef<'str, 'ctx> {
245    pub fn from_id(ctx: &'ctx Context<'str>, id: BlockParamId) -> Self {
246        Self::new(ModuleView::new(ctx), id)
247    }
248}
249
250impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 'ctx, 'str> for BlockParamRef<'str, 'ctx> {
251    fn ctx(&'s self) -> &'ctx Context<'str> {
252        // Module-scope-only escape hatch: shared-only reads go through
253        // `host().shr()`; only whole-module walks (callees/callers) reach here,
254        // and those panic on a checked-out host by design (context-split Pin B).
255        self.view.context()
256    }
257}
258
259impl<'str: 'ctx, 'ctx, R> Named for BlockParamRef<'str, 'ctx, R>
260where
261    R: QCodeView<'ctx, 'str>,
262{
263    fn name(&self) -> Option<&str> {
264        self.view.block_param(self.id).name.as_deref()
265    }
266}
267
268impl<'str: 'ctx, 'ctx, R> Display for BlockParamRef<'str, 'ctx, R>
269where
270    R: QCodeView<'ctx, 'str>,
271{
272    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
273        BlockParamRef::fmt(self, f)
274    }
275}
276
277impl<'str: 'ctx, 'ctx, R> Value<'str, 'ctx> for BlockParamRef<'str, 'ctx, R>
278where
279    R: QCodeView<'ctx, 'str>,
280{
281    fn id(&self) -> ValueId {
282        self.id()
283    }
284
285    fn size(&self) -> usize {
286        BlockParamRef::size(self)
287    }
288}
289
290pub type BlockParamMutRef<'str, 'ctx> = BaseRef<&'ctx mut Context<'str>, BlockParamId>;
291
292impl<'str, 'ctx> BlockParamMutRef<'str, 'ctx> {
293    fn inner_mut(&mut self) -> &mut BlockParam<'str> {
294        self.ctx.block_param_mut(self.id)
295    }
296
297    /// Record the source value this param promotes (see [`BlockParam::origin`]).
298    pub fn set_origin(&mut self, origin: ValueId) {
299        let func = self.id.func;
300        self.inner_mut().origin = Some(origin.localize(func));
301    }
302
303    pub fn constrain_size(&mut self, size: usize) {
304        let current = self.size();
305        if current == 0 {
306            self.set_size(size);
307        } else {
308            assert_eq!(
309                current, size,
310                "block parameter size mismatch for {}: existing {} bytes, new {} bytes",
311                self, current, size
312            );
313        }
314    }
315
316    pub fn as_ref(&self) -> BlockParamRef<'str, '_> {
317        BlockParamRef::new(ModuleView::new(self.ctx), self.id)
318    }
319}
320
321// The own-param mutation verbs, written once over any [`QCodeMut`] backing —
322// `&mut Context` (module) and `BodyMut` (checked-out function pass).
323impl<'str, H: QCodeMut<'str>> BaseRef<H, BlockParamId> {
324    /// Resize this parameter: mint an int type in shared storage and retype the
325    /// param in its owning function's arena.
326    pub fn set_size(&mut self, size: usize) {
327        let type_id = self.ctx.shr().types.get_or_make_int(size);
328        self.ctx.block_param_mut(self.id).type_id = type_id;
329    }
330
331    /// Renames this parameter in its owning function's local name table
332    /// (own-param edit, host-routed). Errors only on a duplicate name.
333    pub fn rename_local(&mut self, name: Cow<'str, str>) -> Result<()> {
334        let old_name = self
335            .ctx
336            .body(self.id.func)
337            .block_param(self.id)
338            .name
339            .as_deref()
340            .map(str::to_owned);
341        self.ctx
342            .register_body_name(self.id.into(), name.clone(), old_name.as_deref())?;
343        self.ctx.block_param_mut(self.id).name = Some(name);
344        Ok(())
345    }
346}
347
348impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 's, 'str> for BlockParamMutRef<'str, 'ctx> {
349    fn ctx(&'s self) -> &'s Context<'str> {
350        self.ctx
351    }
352}
353
354impl<'s, 'ctx: 's, 'str: 'ctx> WithCtxMut<'s, 'str> for BlockParamMutRef<'str, 'ctx> {
355    fn ctx_mut(&'s mut self) -> &'s mut Context<'str> {
356        self.ctx
357    }
358}
359
360impl Named for BlockParamMutRef<'_, '_> {
361    fn name(&self) -> Option<&str> {
362        self.ctx.block_param(self.id).name.as_deref()
363    }
364}
365
366impl Display for BlockParamMutRef<'_, '_> {
367    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
368        self.as_ref().fmt(f)
369    }
370}
371
372impl<'str, 'ctx> Value<'str, 'ctx> for BlockParamMutRef<'str, 'ctx> {
373    fn id(&self) -> ValueId {
374        self.id()
375    }
376
377    fn size(&self) -> usize {
378        self.as_ref().size()
379    }
380}
381
382// Renaming works over any mutation host (param names are function-local).
383// `Named` stays concrete: its signature-pinned return lifetime needs `'str` to
384// outlive the `&self` borrow, which a generic `H` cannot prove.
385impl<'str, 'ctx, H: QCodeMut<'str>> Renameable<'str, 'ctx> for BaseRef<H, BlockParamId>
386where
387    Self: Named,
388{
389    fn rename(&mut self, name: Cow<'str, str>) -> Result<()> {
390        self.rename_local(name)
391    }
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397    use crate::{
398        context::Context,
399        value::{BasicBlock, FunctionBody},
400    };
401
402    #[test]
403    fn block_param_storage_is_local_and_refs_qualify_with_param_function() {
404        let mut ctx = Context::new();
405        let func = FunctionBody::make(&mut ctx, "local_param_storage".into())
406            .unwrap()
407            .id;
408        let block_id = BasicBlock::make(&mut ctx, func).id;
409        let param_id = BasicBlock::from_id_mut(&mut ctx, block_id).push_param(8).id;
410
411        BlockParam::from_id_mut(&mut ctx, param_id).set_origin(ValueId::BlockParam(param_id));
412
413        let raw = ctx.block_param(param_id);
414        assert_eq!(raw.parent_id(), Some(block_id.local));
415        assert_eq!(
416            raw.origin_id(),
417            Some(LocalValueId::BlockParam(param_id.local))
418        );
419
420        let param = BlockParam::from_id(&ctx, param_id);
421        assert_eq!(param.parent().map(|block| block.id), Some(block_id));
422        assert_eq!(param.origin(), Some(ValueId::BlockParam(param_id)));
423    }
424
425    #[test]
426    #[cfg(debug_assertions)]
427    #[should_panic(expected = "localize: foreign block-param operand")]
428    fn block_param_origin_rejects_foreign_function_value() {
429        let mut ctx = Context::new();
430        let a = FunctionBody::make(&mut ctx, "origin_a".into()).unwrap().id;
431        let b = FunctionBody::make(&mut ctx, "origin_b".into()).unwrap().id;
432        let a_block = BasicBlock::make(&mut ctx, a).id;
433        let b_block = BasicBlock::make(&mut ctx, b).id;
434        let a_param = BasicBlock::from_id_mut(&mut ctx, a_block).push_param(8).id;
435        let b_param = BasicBlock::from_id_mut(&mut ctx, b_block).push_param(8).id;
436
437        BlockParam::from_id_mut(&mut ctx, b_param).set_origin(ValueId::BlockParam(a_param));
438    }
439
440    #[test]
441    fn make_block_param_sets_index_and_size() {
442        let mut ctx = Context::new();
443        let block_id = {
444            let __f = ctx.anon_function();
445            BasicBlock::make(&mut ctx, __f)
446        }
447        .id;
448
449        let p0_id = BasicBlock::from_id_mut(&mut ctx, block_id).push_param(8).id;
450        let p1_id = BasicBlock::from_id_mut(&mut ctx, block_id).push_param(4).id;
451
452        let p0 = BlockParam::from_id(&ctx, p0_id);
453        let p1 = BlockParam::from_id(&ctx, p1_id);
454        assert_eq!(p0.index(), 0);
455        assert_eq!(p0.size(), 8);
456        assert_eq!(p1.index(), 1);
457        assert_eq!(p1.size(), 4);
458    }
459
460    #[test]
461    fn block_param_display_uses_name_when_set() {
462        let mut ctx = Context::new();
463        let block_id = {
464            let __f = ctx.anon_function();
465            BasicBlock::make(&mut ctx, __f)
466        }
467        .id;
468        let p_id = BasicBlock::from_id_mut(&mut ctx, block_id).push_param(8).id;
469
470        let mut p = BlockParam::from_id_mut(&mut ctx, p_id);
471        p.rename("myval".into()).expect("rename ok");
472        assert_eq!(p.to_string(), "@myval");
473    }
474
475    #[test]
476    fn block_param_display_fallback_when_unnamed() {
477        let mut ctx = Context::new();
478        let block_id = {
479            let __f = ctx.anon_function();
480            BasicBlock::make(&mut ctx, __f)
481        }
482        .id;
483        let p_id = BasicBlock::from_id_mut(&mut ctx, block_id).push_param(4).id;
484        let p = BlockParam::from_id(&ctx, p_id);
485        let s = p.to_string();
486        assert!(s.starts_with("@param"), "expected @param<hex>, got {s}");
487    }
488}