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}