midenc_codegen_masm/
legalization.rs1use 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#[derive(Default)]
33pub struct LegalizeForMasm;
34
35impl LegalizeForMasm {
36 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
87pub 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
100pub 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}