Skip to main content

midenc_codegen_masm/
legalization.rs

1use alloc::rc::Rc;
2
3use midenc_dialect_arith as arith;
4use midenc_dialect_cf as cf;
5use midenc_dialect_hir as hir;
6use midenc_dialect_scf as scf;
7use midenc_dialect_ub as ub;
8use midenc_dialect_wasm as wasm;
9use midenc_hir::{
10    Context, EntityMut, Operation, OperationName, Report,
11    conversion::{
12        ConversionConfig, ConversionPatternSet, ConversionTarget, DynamicLegalityResult,
13        apply_full_conversion,
14    },
15    dialects::{builtin, debuginfo},
16    pass::{Pass, PassExecutionState, PostPassStatus},
17};
18
19use crate::HirLowering;
20
21midenc_hir::inventory::submit!(::midenc_hir::pass::registry::PassInfo::new::<LegalizeForMasm>(
22    LegalizeForMasm::ARGUMENT,
23    "legalize HIR for MASM codegen"
24));
25
26/// A dialect conversion pass that validates IR against the set of operations MASM codegen can
27/// lower.
28///
29/// This pass is intentionally owned by `midenc-codegen-masm`: it builds the MASM-specific
30/// legalization target, runs full dialect conversion, and fails before `ToMasmComponent` can
31/// encounter unsupported operations.
32#[derive(Default)]
33pub struct LegalizeForMasm;
34
35impl LegalizeForMasm {
36    /// Command-line/pass-pipeline argument for this pass.
37    pub const ARGUMENT: &'static str = "legalize-for-masm";
38}
39
40impl Pass for LegalizeForMasm {
41    type Target = Operation;
42
43    fn name(&self) -> &'static str {
44        "legalize-for-masm"
45    }
46
47    fn argument(&self) -> &'static str {
48        Self::ARGUMENT
49    }
50
51    fn description(&self) -> &'static str {
52        "Legalizes HIR to the set of operations supported by MASM codegen"
53    }
54
55    fn can_schedule_on(&self, _name: &OperationName) -> bool {
56        true
57    }
58
59    fn initialize(&mut self, context: Rc<Context>) -> Result<(), Report> {
60        register_masm_legalization_dialects(&context);
61        Ok(())
62    }
63
64    fn run_on_operation(
65        &mut self,
66        op: EntityMut<'_, Self::Target>,
67        state: &mut PassExecutionState,
68    ) -> Result<(), Report> {
69        let root = op.as_operation_ref();
70        let context = op.context_rc();
71        drop(op);
72
73        let target = masm_legalization_target(context.clone());
74        let patterns = ConversionPatternSet::new(context);
75        let result = apply_full_conversion(root, target, patterns, ConversionConfig::default())?;
76
77        let changed = PostPassStatus::from(result.changed());
78        state.set_post_pass_status(changed);
79        if !changed.ir_changed() {
80            state.preserved_analyses_mut().preserve_all();
81        }
82
83        Ok(())
84    }
85}
86
87/// Build a conversion target that represents the final IR accepted by MASM codegen.
88///
89/// Structural builtin operations such as modules and functions are legal containers, but their
90/// nested operations are still checked. Leaf operations in explicitly supported dialects are legal
91/// only when they implement `HirLowering`. `builtin.unrealized_conversion_cast` is always illegal
92/// as a final operation.
93pub fn masm_legalization_target(context: Rc<Context>) -> ConversionTarget {
94    register_masm_legalization_dialects(&context);
95    let mut target = ConversionTarget::new(context);
96    populate_masm_legalization_target(&mut target);
97    target
98}
99
100/// Populate `target` with MASM codegen legality rules.
101///
102/// This helper is exposed so tests and future codegen passes can extend the MASM target while
103/// keeping the base policy centralized in this crate.
104pub fn populate_masm_legalization_target(target: &mut ConversionTarget) {
105    target
106        .add_legal_op::<builtin::World>()
107        .add_legal_op::<builtin::Component>()
108        .add_legal_op::<builtin::Module>()
109        .add_legal_op::<builtin::Interface>()
110        .add_legal_op::<builtin::Function>()
111        .add_legal_op::<builtin::GlobalVariable>()
112        .add_legal_op::<builtin::Segment>()
113        .add_dynamically_legal_op::<builtin::UnrealizedConversionCast, _>(|op| {
114            DynamicLegalityResult::illegal_with_reason(Report::msg(format!(
115                "operation '{}' is temporary dialect-conversion scaffolding and must be \
116                 reconciled or lowered to a real cast before MASM codegen",
117                op.name()
118            )))
119        })
120        .add_dynamically_legal_dialect::<builtin::BuiltinDialect, _>(masm_lowerable_op)
121        .add_dynamically_legal_dialect::<arith::ArithDialect, _>(masm_lowerable_op)
122        .add_dynamically_legal_dialect::<cf::ControlFlowDialect, _>(masm_lowerable_op)
123        .add_dynamically_legal_dialect::<scf::ScfDialect, _>(masm_lowerable_op)
124        .add_dynamically_legal_dialect::<ub::UndefinedBehaviorDialect, _>(masm_lowerable_op)
125        .add_dynamically_legal_dialect::<hir::HirDialect, _>(masm_lowerable_op)
126        .add_dynamically_legal_dialect::<wasm::WasmDialect, _>(masm_lowerable_op)
127        .add_dynamically_legal_dialect::<debuginfo::DebugInfoDialect, _>(masm_lowerable_op);
128}
129
130fn register_masm_legalization_dialects(context: &Rc<Context>) {
131    context.get_or_register_dialect::<builtin::BuiltinDialect>();
132    context.get_or_register_dialect::<arith::ArithDialect>();
133    context.get_or_register_dialect::<cf::ControlFlowDialect>();
134    context.get_or_register_dialect::<scf::ScfDialect>();
135    context.get_or_register_dialect::<ub::UndefinedBehaviorDialect>();
136    context.get_or_register_dialect::<hir::HirDialect>();
137    context.get_or_register_dialect::<wasm::WasmDialect>();
138    context.get_or_register_dialect::<debuginfo::DebugInfoDialect>();
139}
140
141fn masm_lowerable_op(op: &Operation) -> DynamicLegalityResult {
142    if op.implements::<dyn HirLowering>() {
143        DynamicLegalityResult::legal()
144    } else {
145        DynamicLegalityResult::illegal_with_reason(Report::msg(format!(
146            "operation '{}' is in a MASM-supported dialect but does not implement HirLowering",
147            op.name()
148        )))
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use alloc::format;
155
156    use midenc_dialect_arith::ArithOpBuilder;
157    use midenc_dialect_hir::HirOpBuilder;
158    use midenc_hir::{SourceSpan, Type, dialects::builtin::BuiltinOpBuilder, testing::Test};
159
160    use super::*;
161
162    #[test]
163    fn masm_supported_ops_pass_legalization() {
164        let mut test = Test::new("masm_supported_ops_pass_legalization", &[], &[Type::U32]);
165        {
166            let mut builder = test.function_builder();
167            let value = builder.u32(7, SourceSpan::UNKNOWN);
168            builder.ret([value], SourceSpan::UNKNOWN).unwrap();
169        }
170
171        test.apply_pass::<LegalizeForMasm>(true).unwrap();
172    }
173
174    #[test]
175    fn unsupported_hir_ops_fail_legalization() {
176        let mut test = Test::new("unsupported_hir_ops_fail_legalization", &[], &[]);
177        {
178            let mut builder = test.function_builder();
179            let _bytes = builder.bytes(&[1, 2, 3, 4], SourceSpan::UNKNOWN).unwrap();
180            builder.ret(None, SourceSpan::UNKNOWN).unwrap();
181        }
182
183        let err = test.apply_pass::<LegalizeForMasm>(false).unwrap_err();
184        let message = format!("{err}");
185        assert!(message.contains("hir.bytes"));
186        assert!(message.contains("does not implement HirLowering"));
187    }
188
189    #[test]
190    fn unreconciled_unrealized_conversion_casts_fail_legalization() {
191        let mut test = Test::new(
192            "unreconciled_unrealized_conversion_casts_fail_legalization",
193            &[Type::U32],
194            &[Type::I32],
195        );
196        {
197            let mut builder = test.function_builder();
198            let entry = builder.entry_block();
199            let arg = entry.borrow().arguments()[0].borrow().as_value_ref();
200            let cast =
201                builder.unrealized_conversion_cast(arg, Type::I32, SourceSpan::UNKNOWN).unwrap();
202            builder.ret([cast], SourceSpan::UNKNOWN).unwrap();
203        }
204
205        let err = test.apply_pass::<LegalizeForMasm>(false).unwrap_err();
206        let message = format!("{err}");
207        assert!(message.contains("builtin.unrealized_conversion_cast"));
208        assert!(message.contains("temporary dialect-conversion scaffolding"));
209    }
210}