1use std::collections::HashMap;
7
8use crate::codegen_binja;
9use crate::config::GenTarget;
10use crate::tree;
11use crate::types::ValidatedDef;
12
13use super::{CodegenBackend, CodegenError, FlowConfig, OperandKind};
14
15pub struct BinjaBackend;
17
18pub struct BinjaOptions {
20 pub architecture_name: String,
21 pub address_size: u32,
22 pub default_int_size: u32,
23 pub max_instr_length: u32,
24 pub endianness: String,
25 pub register_names: Vec<String>,
26 pub register_size: u32,
27 pub stack_pointer: Option<String>,
28 pub link_register: Option<String>,
29 pub bytes_per_unit: u32,
30 pub display_prefixes: HashMap<String, String>,
31 pub operand_types: HashMap<String, OperandKind>,
32 pub flow: FlowConfig,
33}
34
35impl CodegenBackend for BinjaBackend {
36 fn lang(&self) -> &str {
37 "binja"
38 }
39
40 fn validate_lang_options(&self, options: &toml::Value) -> Result<(), Vec<String>> {
41 let table = match options {
42 toml::Value::Table(t) => t,
43 _ => return Ok(()),
44 };
45
46 let mut errors = Vec::new();
47
48 if !table.contains_key("architecture_name") {
49 errors.push("missing required lang_option: architecture_name".to_string());
50 }
51 if !table.contains_key("register_names") {
52 errors.push("missing required lang_option: register_names".to_string());
53 }
54
55 let known_keys = [
56 "architecture_name",
57 "address_size",
58 "default_int_size",
59 "max_instr_length",
60 "endianness",
61 "register_names",
62 "register_size",
63 "stack_pointer",
64 "link_register",
65 "bytes_per_unit",
66 "display_prefixes",
67 "operand_types",
68 "flow",
69 ];
70 for key in table.keys() {
71 if !known_keys.contains(&key.as_str()) {
72 errors.push(format!("unknown lang_option: {}", key));
73 }
74 }
75
76 if errors.is_empty() {
77 Ok(())
78 } else {
79 Err(errors)
80 }
81 }
82
83 fn generate(&self, ir: &ValidatedDef, config: &GenTarget) -> Result<String, CodegenError> {
84 let tree = tree::build_tree(ir);
85 let opts = parse_binja_options(config).map_err(CodegenError::Internal)?;
86 Ok(codegen_binja::generate_binja_code(ir, &tree, &opts))
87 }
88
89 fn formatter_command(&self) -> Option<&[&str]> {
90 Some(&["ruff", "format"])
91 }
92}
93
94fn parse_binja_options(config: &GenTarget) -> Result<BinjaOptions, String> {
95 let table = match &config.lang_options {
96 Some(toml::Value::Table(t)) => t.clone(),
97 Some(_) => return Err("lang_options must be a table".to_string()),
98 None => return Err("Binary Ninja backend requires lang_options configuration".to_string()),
99 };
100
101 let architecture_name = table
102 .get("architecture_name")
103 .and_then(|v| v.as_str())
104 .ok_or("architecture_name must be a string")?
105 .to_string();
106
107 let register_names = table
108 .get("register_names")
109 .and_then(|v| v.as_array())
110 .ok_or("register_names must be an array")?
111 .iter()
112 .map(|v| {
113 v.as_str()
114 .ok_or("register_names entries must be strings")
115 .map(|s| s.to_string())
116 })
117 .collect::<Result<Vec<String>, _>>()?;
118
119 if register_names.is_empty() {
120 return Err("register_names must not be empty".to_string());
121 }
122
123 let address_size = table
124 .get("address_size")
125 .and_then(|v| v.as_integer())
126 .unwrap_or(4) as u32;
127
128 let default_int_size = table
129 .get("default_int_size")
130 .and_then(|v| v.as_integer())
131 .unwrap_or(address_size as i64) as u32;
132
133 let max_instr_length = table
134 .get("max_instr_length")
135 .and_then(|v| v.as_integer())
136 .unwrap_or(4) as u32;
137
138 let endianness = table
139 .get("endianness")
140 .and_then(|v| v.as_str())
141 .unwrap_or("LittleEndian")
142 .to_string();
143
144 let register_size = table
145 .get("register_size")
146 .and_then(|v| v.as_integer())
147 .unwrap_or(address_size as i64) as u32;
148
149 let stack_pointer = table
150 .get("stack_pointer")
151 .and_then(|v| v.as_str())
152 .map(|s| s.to_string());
153
154 let link_register = table
155 .get("link_register")
156 .and_then(|v| v.as_str())
157 .map(|s| s.to_string());
158
159 let bytes_per_unit = table
160 .get("bytes_per_unit")
161 .and_then(|v| v.as_integer())
162 .unwrap_or(1) as u32;
163
164 let display_prefixes = if let Some(dp) = table.get("display_prefixes") {
165 let dp_table = dp.as_table().ok_or("display_prefixes must be a table")?;
166 let mut map = HashMap::new();
167 for (key, val) in dp_table {
168 let prefix = val
169 .as_str()
170 .ok_or("display_prefixes values must be strings")?;
171 map.insert(key.clone(), prefix.to_string());
172 }
173 map
174 } else {
175 HashMap::new()
176 };
177
178 let operand_types = if let Some(ot) = table.get("operand_types") {
179 let ot_table = ot.as_table().ok_or("operand_types must be a table")?;
180 let mut map = HashMap::new();
181 for (key, val) in ot_table {
182 let kind_str = val.as_str().ok_or("operand_types values must be strings")?;
183 let kind = match kind_str {
184 "register" | "reg" => OperandKind::Register,
185 "immediate" | "imm" => OperandKind::Immediate,
186 "address" | "addr" => OperandKind::Address,
187 "memory" | "mem" => OperandKind::Memory,
188 _ => {
189 return Err(format!(
190 "unknown operand type '{}' for field '{}'",
191 kind_str, key
192 ));
193 }
194 };
195 map.insert(key.clone(), kind);
196 }
197 map
198 } else {
199 HashMap::new()
200 };
201
202 let flow = if let Some(f) = table.get("flow") {
203 let f_table = f.as_table().ok_or("flow must be a table")?;
204 FlowConfig {
205 calls: parse_string_array(f_table, "calls")?,
206 branches: parse_string_array(f_table, "branches")?,
207 unconditional_branches: parse_string_array(f_table, "unconditional_branches")?,
208 returns: parse_string_array(f_table, "returns")?,
209 stops: parse_string_array(f_table, "stops")?,
210 }
211 } else {
212 FlowConfig::default()
213 };
214
215 Ok(BinjaOptions {
216 architecture_name,
217 address_size,
218 default_int_size,
219 max_instr_length,
220 endianness,
221 register_names,
222 register_size,
223 stack_pointer,
224 link_register,
225 bytes_per_unit,
226 display_prefixes,
227 operand_types,
228 flow,
229 })
230}
231
232fn parse_string_array(
233 table: &toml::map::Map<String, toml::Value>,
234 key: &str,
235) -> Result<Vec<String>, String> {
236 if let Some(v) = table.get(key) {
237 v.as_array()
238 .ok_or(format!("{} must be an array", key))?
239 .iter()
240 .map(|v| {
241 v.as_str()
242 .ok_or(format!("{} entries must be strings", key))
243 .map(|s| s.to_string())
244 })
245 .collect()
246 } else {
247 Ok(Vec::new())
248 }
249}