Skip to main content

yulang_native/
abi.rs

1//! Backend-neutral ABI lowering for native closure IR.
2//!
3//! This is the boundary Cranelift should consume later.  It keeps closure
4//! allocation, environment loads, direct calls, and indirect closure calls
5//! explicit without committing to a machine-level value representation yet.
6
7use yulang_typed_ir as typed_ir;
8
9use crate::closure::{
10    NativeClosureBlock, NativeClosureFunction, NativeClosureModule, NativeClosureStmt,
11};
12use crate::control_ir::{
13    BlockId, NativeLiteral, NativeRecordField, NativeStmt, NativeTerminator, ValueId,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct NativeAbiModule {
18    pub functions: Vec<NativeAbiFunction>,
19    pub roots: Vec<NativeAbiFunction>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct NativeAbiFunction {
24    pub name: String,
25    pub params: Vec<ValueId>,
26    pub environment_slots: usize,
27    pub blocks: Vec<NativeAbiBlock>,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct NativeAbiBlock {
32    pub id: BlockId,
33    pub params: Vec<ValueId>,
34    pub stmts: Vec<NativeAbiStmt>,
35    pub terminator: NativeTerminator,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum NativeAbiStmt {
40    Literal {
41        dest: ValueId,
42        literal: NativeLiteral,
43    },
44    Primitive {
45        dest: ValueId,
46        op: typed_ir::PrimitiveOp,
47        args: Vec<ValueId>,
48    },
49    DirectCall {
50        dest: ValueId,
51        target: String,
52        args: Vec<ValueId>,
53    },
54    Tuple {
55        dest: ValueId,
56        items: Vec<ValueId>,
57    },
58    Record {
59        dest: ValueId,
60        base: Option<ValueId>,
61        fields: Vec<NativeRecordField>,
62    },
63    RecordWithoutFields {
64        dest: ValueId,
65        base: ValueId,
66        fields: Vec<typed_ir::Name>,
67    },
68    Variant {
69        dest: ValueId,
70        tag: typed_ir::Name,
71        value: Option<ValueId>,
72    },
73    Select {
74        dest: ValueId,
75        base: ValueId,
76        field: typed_ir::Name,
77    },
78    TupleGet {
79        dest: ValueId,
80        tuple: ValueId,
81        index: usize,
82    },
83    VariantTagEq {
84        dest: ValueId,
85        variant: ValueId,
86        tag: typed_ir::Name,
87    },
88    VariantPayload {
89        dest: ValueId,
90        variant: ValueId,
91    },
92    ValueEq {
93        dest: ValueId,
94        left: ValueId,
95        right: ValueId,
96    },
97    BoolAnd {
98        dest: ValueId,
99        left: ValueId,
100        right: ValueId,
101    },
102    LoadEnv {
103        dest: ValueId,
104        slot: usize,
105    },
106    AllocateClosure {
107        dest: ValueId,
108        target: String,
109        environment: Vec<ValueId>,
110    },
111    IndirectClosureCall {
112        dest: ValueId,
113        callee: ValueId,
114        args: Vec<ValueId>,
115    },
116}
117
118pub fn lower_closure_module_to_abi(module: &NativeClosureModule) -> NativeAbiModule {
119    NativeAbiModule {
120        functions: module.functions.iter().map(lower_function).collect(),
121        roots: module.roots.iter().map(lower_function).collect(),
122    }
123}
124
125fn lower_function(function: &NativeClosureFunction) -> NativeAbiFunction {
126    NativeAbiFunction {
127        name: function.name.clone(),
128        params: function.abi.params.clone(),
129        environment_slots: function.abi.environment.slots,
130        blocks: function.blocks.iter().map(lower_block).collect(),
131    }
132}
133
134fn lower_block(block: &NativeClosureBlock) -> NativeAbiBlock {
135    NativeAbiBlock {
136        id: block.id,
137        params: block.params.clone(),
138        stmts: block.stmts.iter().map(lower_stmt).collect(),
139        terminator: block.terminator.clone(),
140    }
141}
142
143fn lower_stmt(stmt: &NativeClosureStmt) -> NativeAbiStmt {
144    match stmt {
145        NativeClosureStmt::LoadEnv { dest, slot } => NativeAbiStmt::LoadEnv {
146            dest: *dest,
147            slot: *slot,
148        },
149        NativeClosureStmt::MakeClosure {
150            dest,
151            target,
152            environment,
153        } => NativeAbiStmt::AllocateClosure {
154            dest: *dest,
155            target: target.clone(),
156            environment: environment.iter().map(|capture| capture.value).collect(),
157        },
158        NativeClosureStmt::ClosureCall { dest, callee, args } => {
159            NativeAbiStmt::IndirectClosureCall {
160                dest: *dest,
161                callee: *callee,
162                args: args.clone(),
163            }
164        }
165        NativeClosureStmt::Native(stmt) => lower_native_stmt(stmt),
166    }
167}
168
169fn lower_native_stmt(stmt: &NativeStmt) -> NativeAbiStmt {
170    match stmt {
171        NativeStmt::Literal { dest, literal } => NativeAbiStmt::Literal {
172            dest: *dest,
173            literal: literal.clone(),
174        },
175        NativeStmt::Primitive { dest, op, args } => NativeAbiStmt::Primitive {
176            dest: *dest,
177            op: *op,
178            args: args.clone(),
179        },
180        NativeStmt::DirectCall { dest, target, args } => NativeAbiStmt::DirectCall {
181            dest: *dest,
182            target: target.clone(),
183            args: args.clone(),
184        },
185        NativeStmt::Tuple { dest, items } => NativeAbiStmt::Tuple {
186            dest: *dest,
187            items: items.clone(),
188        },
189        NativeStmt::Record { dest, base, fields } => NativeAbiStmt::Record {
190            dest: *dest,
191            base: *base,
192            fields: fields.clone(),
193        },
194        NativeStmt::RecordWithoutFields { dest, base, fields } => {
195            NativeAbiStmt::RecordWithoutFields {
196                dest: *dest,
197                base: *base,
198                fields: fields.clone(),
199            }
200        }
201        NativeStmt::Variant { dest, tag, value } => NativeAbiStmt::Variant {
202            dest: *dest,
203            tag: tag.clone(),
204            value: *value,
205        },
206        NativeStmt::Select { dest, base, field } => NativeAbiStmt::Select {
207            dest: *dest,
208            base: *base,
209            field: field.clone(),
210        },
211        NativeStmt::TupleGet { dest, tuple, index } => NativeAbiStmt::TupleGet {
212            dest: *dest,
213            tuple: *tuple,
214            index: *index,
215        },
216        NativeStmt::VariantTagEq { dest, variant, tag } => NativeAbiStmt::VariantTagEq {
217            dest: *dest,
218            variant: *variant,
219            tag: tag.clone(),
220        },
221        NativeStmt::VariantPayload { dest, variant } => NativeAbiStmt::VariantPayload {
222            dest: *dest,
223            variant: *variant,
224        },
225        NativeStmt::ValueEq { dest, left, right } => NativeAbiStmt::ValueEq {
226            dest: *dest,
227            left: *left,
228            right: *right,
229        },
230        NativeStmt::BoolAnd { dest, left, right } => NativeAbiStmt::BoolAnd {
231            dest: *dest,
232            left: *left,
233            right: *right,
234        },
235        NativeStmt::MakeClosure {
236            dest,
237            target,
238            captures,
239        } => NativeAbiStmt::AllocateClosure {
240            dest: *dest,
241            target: target.clone(),
242            environment: captures.clone(),
243        },
244        NativeStmt::ClosureCall { dest, callee, args } => NativeAbiStmt::IndirectClosureCall {
245            dest: *dest,
246            callee: *callee,
247            args: args.clone(),
248        },
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use crate::closure::{NativeClosureCapture, closure_convert_module};
255    use crate::control_ir::{NativeBlock, NativeFunction, NativeModule, NativeTerminator};
256
257    use super::*;
258
259    #[test]
260    fn lowers_closure_function_abi_shape() {
261        let function = NativeFunction {
262            name: "root#lambda0".to_string(),
263            captures: vec![ValueId(0)],
264            params: vec![ValueId(0), ValueId(1)],
265            blocks: vec![NativeBlock {
266                id: BlockId(0),
267                params: vec![ValueId(0), ValueId(1)],
268                stmts: Vec::new(),
269                terminator: NativeTerminator::Return(ValueId(1)),
270            }],
271        };
272        let module = NativeModule {
273            functions: vec![function],
274            roots: Vec::new(),
275        };
276        let closure_module = closure_convert_module(&module);
277
278        let abi = lower_closure_module_to_abi(&closure_module);
279
280        assert_eq!(
281            abi.functions,
282            vec![NativeAbiFunction {
283                name: "root#lambda0".to_string(),
284                params: vec![ValueId(1)],
285                environment_slots: 1,
286                blocks: vec![NativeAbiBlock {
287                    id: BlockId(0),
288                    params: vec![ValueId(1)],
289                    stmts: vec![NativeAbiStmt::LoadEnv {
290                        dest: ValueId(0),
291                        slot: 0,
292                    }],
293                    terminator: NativeTerminator::Return(ValueId(1)),
294                }],
295            }]
296        );
297    }
298
299    #[test]
300    fn lowers_closure_allocation_and_indirect_call() {
301        let closure_module = NativeClosureModule {
302            functions: Vec::new(),
303            roots: vec![crate::closure::NativeClosureFunction {
304                name: "root".to_string(),
305                params: Vec::new(),
306                abi: crate::closure::NativeClosureAbi {
307                    code: crate::closure::NativeClosureCodeRef {
308                        function: "root".to_string(),
309                    },
310                    environment: crate::closure::NativeClosureEnvRef { slots: 0 },
311                    params: Vec::new(),
312                },
313                environment: crate::closure::NativeClosureEnvironment { slots: Vec::new() },
314                blocks: vec![crate::closure::NativeClosureBlock {
315                    id: BlockId(0),
316                    params: Vec::new(),
317                    stmts: vec![
318                        NativeClosureStmt::MakeClosure {
319                            dest: ValueId(2),
320                            target: "root#lambda0".to_string(),
321                            environment: vec![
322                                NativeClosureCapture {
323                                    slot: 0,
324                                    value: ValueId(0),
325                                },
326                                NativeClosureCapture {
327                                    slot: 1,
328                                    value: ValueId(1),
329                                },
330                            ],
331                        },
332                        NativeClosureStmt::ClosureCall {
333                            dest: ValueId(3),
334                            callee: ValueId(2),
335                            args: vec![ValueId(4)],
336                        },
337                    ],
338                    terminator: NativeTerminator::Return(ValueId(3)),
339                }],
340            }],
341        };
342
343        let abi = lower_closure_module_to_abi(&closure_module);
344
345        assert_eq!(
346            abi.roots[0].blocks[0].stmts,
347            vec![
348                NativeAbiStmt::AllocateClosure {
349                    dest: ValueId(2),
350                    target: "root#lambda0".to_string(),
351                    environment: vec![ValueId(0), ValueId(1)],
352                },
353                NativeAbiStmt::IndirectClosureCall {
354                    dest: ValueId(3),
355                    callee: ValueId(2),
356                    args: vec![ValueId(4)],
357                },
358            ]
359        );
360    }
361}