Skip to main content

yulang_native/
abi_subset.rs

1use std::fmt;
2
3use yulang_typed_ir as typed_ir;
4
5use crate::abi::{NativeAbiBlock, NativeAbiFunction, NativeAbiModule, NativeAbiStmt};
6use crate::control_ir::NativeLiteral;
7
8pub type NativeAbiSubsetResult<T> = Result<T, NativeAbiSubsetError>;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum NativeAbiSubsetError {
12    UnsupportedLiteral {
13        function: String,
14        literal: NativeLiteral,
15    },
16    UnsupportedPrimitive {
17        function: String,
18        op: typed_ir::PrimitiveOp,
19    },
20}
21
22impl fmt::Display for NativeAbiSubsetError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            NativeAbiSubsetError::UnsupportedLiteral { function, literal } => write!(
26                f,
27                "native Cranelift prototype does not support literal {literal:?} in `{function}`"
28            ),
29            NativeAbiSubsetError::UnsupportedPrimitive { function, op } => write!(
30                f,
31                "native Cranelift prototype does not support primitive {op:?} in `{function}`"
32            ),
33        }
34    }
35}
36
37impl std::error::Error for NativeAbiSubsetError {}
38
39pub fn validate_cranelift_prototype_subset(module: &NativeAbiModule) -> NativeAbiSubsetResult<()> {
40    for function in module.functions.iter().chain(&module.roots) {
41        validate_function(function)?;
42    }
43    Ok(())
44}
45
46fn validate_function(function: &NativeAbiFunction) -> NativeAbiSubsetResult<()> {
47    for block in &function.blocks {
48        validate_block(function, block)?;
49    }
50    Ok(())
51}
52
53fn validate_block(
54    function: &NativeAbiFunction,
55    block: &NativeAbiBlock,
56) -> NativeAbiSubsetResult<()> {
57    for stmt in &block.stmts {
58        validate_stmt(function, stmt)?;
59    }
60    Ok(())
61}
62
63fn validate_stmt(function: &NativeAbiFunction, stmt: &NativeAbiStmt) -> NativeAbiSubsetResult<()> {
64    match stmt {
65        NativeAbiStmt::Literal { literal, .. } if supported_literal(literal) => Ok(()),
66        NativeAbiStmt::Literal { literal, .. } => Err(NativeAbiSubsetError::UnsupportedLiteral {
67            function: function.name.clone(),
68            literal: literal.clone(),
69        }),
70        NativeAbiStmt::Primitive { op, .. } if supported_primitive(*op) => Ok(()),
71        NativeAbiStmt::Primitive { op, .. } => Err(NativeAbiSubsetError::UnsupportedPrimitive {
72            function: function.name.clone(),
73            op: *op,
74        }),
75        NativeAbiStmt::DirectCall { .. } => Ok(()),
76        NativeAbiStmt::Tuple { .. }
77        | NativeAbiStmt::Record { .. }
78        | NativeAbiStmt::RecordWithoutFields { .. }
79        | NativeAbiStmt::Variant { .. }
80        | NativeAbiStmt::Select { .. }
81        | NativeAbiStmt::TupleGet { .. }
82        | NativeAbiStmt::VariantTagEq { .. }
83        | NativeAbiStmt::VariantPayload { .. }
84        | NativeAbiStmt::ValueEq { .. }
85        | NativeAbiStmt::BoolAnd { .. } => Ok(()),
86        NativeAbiStmt::LoadEnv { .. }
87        | NativeAbiStmt::AllocateClosure { .. }
88        | NativeAbiStmt::IndirectClosureCall { .. } => Ok(()),
89    }
90}
91
92fn supported_literal(literal: &NativeLiteral) -> bool {
93    matches!(
94        literal,
95        NativeLiteral::Int(_)
96            | NativeLiteral::Float(_)
97            | NativeLiteral::Bool(_)
98            | NativeLiteral::Unit
99    )
100}
101
102fn supported_primitive(op: typed_ir::PrimitiveOp) -> bool {
103    matches!(
104        op,
105        typed_ir::PrimitiveOp::BoolNot
106            | typed_ir::PrimitiveOp::BoolEq
107            | typed_ir::PrimitiveOp::IntAdd
108            | typed_ir::PrimitiveOp::IntSub
109            | typed_ir::PrimitiveOp::IntMul
110            | typed_ir::PrimitiveOp::IntDiv
111            | typed_ir::PrimitiveOp::IntEq
112            | typed_ir::PrimitiveOp::IntLt
113            | typed_ir::PrimitiveOp::IntLe
114            | typed_ir::PrimitiveOp::IntGt
115            | typed_ir::PrimitiveOp::IntGe
116            | typed_ir::PrimitiveOp::FloatAdd
117            | typed_ir::PrimitiveOp::FloatSub
118            | typed_ir::PrimitiveOp::FloatMul
119            | typed_ir::PrimitiveOp::FloatDiv
120            | typed_ir::PrimitiveOp::FloatEq
121            | typed_ir::PrimitiveOp::FloatLt
122            | typed_ir::PrimitiveOp::FloatLe
123            | typed_ir::PrimitiveOp::FloatGt
124            | typed_ir::PrimitiveOp::FloatGe
125    )
126}
127
128#[cfg(test)]
129mod tests {
130    use crate::abi::{NativeAbiBlock, NativeAbiFunction, NativeAbiModule, NativeAbiStmt};
131    use crate::control_ir::{BlockId, NativeTerminator, ValueId};
132
133    use super::*;
134
135    #[test]
136    fn accepts_primitive_direct_call_subset() {
137        let module = NativeAbiModule {
138            functions: vec![NativeAbiFunction {
139                name: "add".to_string(),
140                params: vec![ValueId(0), ValueId(1)],
141                environment_slots: 0,
142                blocks: vec![NativeAbiBlock {
143                    id: BlockId(0),
144                    params: Vec::new(),
145                    stmts: vec![NativeAbiStmt::Primitive {
146                        dest: ValueId(2),
147                        op: typed_ir::PrimitiveOp::IntAdd,
148                        args: vec![ValueId(0), ValueId(1)],
149                    }],
150                    terminator: NativeTerminator::Return(ValueId(2)),
151                }],
152            }],
153            roots: vec![NativeAbiFunction {
154                name: "root".to_string(),
155                params: Vec::new(),
156                environment_slots: 0,
157                blocks: vec![NativeAbiBlock {
158                    id: BlockId(0),
159                    params: Vec::new(),
160                    stmts: vec![
161                        NativeAbiStmt::Literal {
162                            dest: ValueId(0),
163                            literal: NativeLiteral::Int("1".to_string()),
164                        },
165                        NativeAbiStmt::Literal {
166                            dest: ValueId(1),
167                            literal: NativeLiteral::Int("2".to_string()),
168                        },
169                        NativeAbiStmt::DirectCall {
170                            dest: ValueId(2),
171                            target: "add".to_string(),
172                            args: vec![ValueId(0), ValueId(1)],
173                        },
174                    ],
175                    terminator: NativeTerminator::Return(ValueId(2)),
176                }],
177            }],
178        };
179
180        validate_cranelift_prototype_subset(&module).expect("subset");
181    }
182
183    #[test]
184    fn rejects_string_literal_before_runtime_string_abi_exists() {
185        let module = single_stmt_module(NativeAbiStmt::Literal {
186            dest: ValueId(0),
187            literal: NativeLiteral::String("hello".to_string()),
188        });
189
190        assert_eq!(
191            validate_cranelift_prototype_subset(&module),
192            Err(NativeAbiSubsetError::UnsupportedLiteral {
193                function: "root".to_string(),
194                literal: NativeLiteral::String("hello".to_string()),
195            })
196        );
197    }
198
199    #[test]
200    fn accepts_closure_statements_for_hosted_closure_prototype() {
201        let module = NativeAbiModule {
202            functions: vec![NativeAbiFunction {
203                name: "add_capture".to_string(),
204                params: vec![ValueId(1)],
205                environment_slots: 1,
206                blocks: vec![NativeAbiBlock {
207                    id: BlockId(0),
208                    params: Vec::new(),
209                    stmts: vec![NativeAbiStmt::LoadEnv {
210                        dest: ValueId(0),
211                        slot: 0,
212                    }],
213                    terminator: NativeTerminator::Return(ValueId(0)),
214                }],
215            }],
216            roots: vec![NativeAbiFunction {
217                name: "root".to_string(),
218                params: Vec::new(),
219                environment_slots: 0,
220                blocks: vec![NativeAbiBlock {
221                    id: BlockId(0),
222                    params: Vec::new(),
223                    stmts: vec![
224                        NativeAbiStmt::Literal {
225                            dest: ValueId(0),
226                            literal: NativeLiteral::Int("1".to_string()),
227                        },
228                        NativeAbiStmt::AllocateClosure {
229                            dest: ValueId(1),
230                            target: "add_capture".to_string(),
231                            environment: vec![ValueId(0)],
232                        },
233                        NativeAbiStmt::IndirectClosureCall {
234                            dest: ValueId(2),
235                            callee: ValueId(1),
236                            args: vec![ValueId(0)],
237                        },
238                    ],
239                    terminator: NativeTerminator::Return(ValueId(2)),
240                }],
241            }],
242        };
243
244        validate_cranelift_prototype_subset(&module).expect("subset");
245    }
246
247    #[test]
248    fn rejects_list_primitive_before_heap_value_abi_exists() {
249        let module = single_stmt_module(NativeAbiStmt::Primitive {
250            dest: ValueId(0),
251            op: typed_ir::PrimitiveOp::ListEmpty,
252            args: Vec::new(),
253        });
254
255        assert_eq!(
256            validate_cranelift_prototype_subset(&module),
257            Err(NativeAbiSubsetError::UnsupportedPrimitive {
258                function: "root".to_string(),
259                op: typed_ir::PrimitiveOp::ListEmpty,
260            })
261        );
262    }
263
264    fn single_stmt_module(stmt: NativeAbiStmt) -> NativeAbiModule {
265        NativeAbiModule {
266            functions: Vec::new(),
267            roots: vec![NativeAbiFunction {
268                name: "root".to_string(),
269                params: Vec::new(),
270                environment_slots: 0,
271                blocks: vec![NativeAbiBlock {
272                    id: BlockId(0),
273                    params: Vec::new(),
274                    stmts: vec![stmt],
275                    terminator: NativeTerminator::Return(ValueId(0)),
276                }],
277            }],
278        }
279    }
280}