fugue_ir/
compiler.rs

1use crate::deserialise::error::Error as DeserialiseError;
2use crate::deserialise::parse::XmlExt;
3use crate::error::Error;
4
5use ahash::AHashMap as Map;
6use ustr::UstrSet;
7
8use std::fs::File;
9use std::io::Read;
10use std::iter::FromIterator;
11use std::path::Path;
12
13#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
14pub struct DataOrganisation {
15    pub absolute_max_alignment: u64,
16    pub machine_alignment: u64,
17    pub default_alignment: u64,
18    pub default_pointer_alignment: u64,
19    pub pointer_size: usize,
20    pub wchar_size: usize,
21    pub short_size: usize,
22    pub integer_size: usize,
23    pub long_size: usize,
24    pub long_long_size: usize,
25    pub float_size: usize,
26    pub double_size: usize,
27    pub long_double_size: usize,
28    pub size_alignment_map: Map<usize, u64>,
29}
30
31impl Default for DataOrganisation {
32    fn default() -> Self {
33        Self {
34            absolute_max_alignment: 0,
35            machine_alignment: 1,
36            default_alignment: 1,
37            default_pointer_alignment: 4,
38            pointer_size: 4,
39            wchar_size: 2,
40            short_size: 2,
41            integer_size: 4,
42            long_size: 4,
43            long_long_size: 8,
44            float_size: 4,
45            double_size: 8,
46            long_double_size: 12,
47            size_alignment_map: Map::from_iter(vec![(1, 1), (2, 2), (4, 4), (8, 8)]),
48        }
49    }
50}
51
52impl DataOrganisation {
53    pub fn from_xml(input: xml::Node) -> Result<Self, DeserialiseError> {
54        if input.tag_name().name() != "data_organization" {
55            return Err(DeserialiseError::TagUnexpected(
56                input.tag_name().name().to_owned(),
57            ));
58        }
59
60        let mut data = Self::default();
61
62        for child in input.children().filter(xml::Node::is_element) {
63            match child.tag_name().name() {
64                "absolute_max_alignment" => {
65                    data.absolute_max_alignment = child.attribute_int("value")?;
66                }
67                "machine_alignment" => {
68                    data.machine_alignment = child.attribute_int("value")?;
69                }
70                "default_alignment" => {
71                    data.default_alignment = child.attribute_int("value")?;
72                }
73                "default_pointer_alignment" => {
74                    data.default_pointer_alignment = child.attribute_int("value")?;
75                }
76                "pointer_size" => {
77                    data.pointer_size = child.attribute_int("value")?;
78                }
79                "wchar_size" => {
80                    data.wchar_size = child.attribute_int("value")?;
81                }
82                "short_size" => {
83                    data.short_size = child.attribute_int("value")?;
84                }
85                "integer_size" => {
86                    data.integer_size = child.attribute_int("value")?;
87                }
88                "long_size" => {
89                    data.long_size = child.attribute_int("value")?;
90                }
91                "long_long_size" => {
92                    data.long_long_size = child.attribute_int("value")?;
93                }
94                "float_size" => {
95                    data.float_size = child.attribute_int("value")?;
96                }
97                "double_size" => {
98                    data.double_size = child.attribute_int("value")?;
99                }
100                "long_double_size" => {
101                    data.long_double_size = child.attribute_int("value")?;
102                }
103                "size_alignment_map" => {
104                    for entry in child
105                        .children()
106                        .filter(|e| e.is_element() && e.tag_name().name() == "entry")
107                    {
108                        data.size_alignment_map.insert(
109                            entry.attribute_int("size")?,
110                            entry.attribute_int("alignment")?,
111                        );
112                    }
113                }
114                _ => (),
115            }
116        }
117
118        Ok(data)
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
123pub struct StackPointer {
124    pub(crate) register: String,
125    pub(crate) space: String,
126}
127
128impl StackPointer {
129    pub fn from_xml(input: xml::Node) -> Result<Self, DeserialiseError> {
130        if input.tag_name().name() != "stackpointer" {
131            return Err(DeserialiseError::TagUnexpected(
132                input.tag_name().name().to_owned(),
133            ));
134        }
135
136        Ok(Self {
137            register: input.attribute_string("register")?,
138            space: input.attribute_string("space")?,
139        })
140    }
141
142    pub fn register(&self) -> &str {
143        &self.register
144    }
145
146    pub fn space(&self) -> &str {
147        &self.space
148    }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
152pub enum ReturnAddress {
153    Register(String),
154    StackRelative { offset: u64, size: usize },
155}
156
157impl ReturnAddress {
158    pub fn from_xml(input: xml::Node) -> Result<Self, DeserialiseError> {
159        if input.tag_name().name() != "returnaddress" {
160            return Err(DeserialiseError::TagUnexpected(
161                input.tag_name().name().to_owned(),
162            ));
163        }
164
165        let mut children = input.children().filter(xml::Node::is_element);
166
167        let node = children
168            .next()
169            .ok_or_else(|| DeserialiseError::Invariant("no children for returnaddress"))?;
170
171        match node.tag_name().name() {
172            "register" => Ok(Self::Register(node.attribute_string("name")?)),
173            "varnode"
174                if node
175                    .attribute_string("space")
176                    .map(|space| space == "stack")
177                    .unwrap_or(false) =>
178            {
179                Ok(Self::StackRelative {
180                    offset: node.attribute_int("offset")?,
181                    size: node.attribute_int("size")?,
182                })
183            }
184            tag => Err(DeserialiseError::TagUnexpected(tag.to_owned())),
185        }
186    }
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
190pub enum PrototypeOperand {
191    Register(String),
192    RegisterJoin(String, String),
193    StackRelative(u64),
194}
195
196impl PrototypeOperand {
197    pub fn from_xml(input: xml::Node) -> Result<Self, DeserialiseError> {
198        match input.tag_name().name() {
199            "addr" => match input.attribute_string("space")?.as_ref() {
200                "join" => Ok(Self::RegisterJoin(
201                    input.attribute_string("piece1")?,
202                    input.attribute_string("piece2")?,
203                )),
204                "stack" => Ok(Self::StackRelative(input.attribute_int("offset")?)),
205                tag => Err(DeserialiseError::TagUnexpected(tag.to_owned())),
206            },
207            "register" => Ok(Self::Register(input.attribute_string("name")?)),
208            tag => Err(DeserialiseError::TagUnexpected(tag.to_owned())),
209        }
210    }
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
214pub struct PrototypeEntry {
215    pub(crate) killed_by_call: bool,
216    pub(crate) min_size: usize,
217    pub(crate) max_size: usize,
218    pub(crate) alignment: u64,
219    pub(crate) meta_type: Option<String>,
220    pub(crate) extension: Option<String>,
221    pub(crate) operand: PrototypeOperand,
222}
223
224impl PrototypeEntry {
225    pub fn from_xml(input: xml::Node, killed_by_call: bool) -> Result<Self, DeserialiseError> {
226        if input.tag_name().name() != "pentry" {
227            return Err(DeserialiseError::TagUnexpected(
228                input.tag_name().name().to_owned(),
229            ));
230        }
231
232        let min_size = input.attribute_int("minsize")?;
233        let max_size = input.attribute_int("maxsize")?;
234        let alignment = input.attribute_int_opt("alignment", 1)?;
235
236        let meta_type = input
237            .attribute_string("metatype")
238            .map(Some)
239            .unwrap_or_default();
240        let extension = input
241            .attribute_string("extension")
242            .map(Some)
243            .unwrap_or_default();
244
245        let node = input.children().filter(xml::Node::is_element).next();
246        if node.is_none() {
247            return Err(DeserialiseError::Invariant(
248                "compiler specification prototype entry does not define an operand",
249            ));
250        }
251
252        let operand = PrototypeOperand::from_xml(node.unwrap())?;
253
254        Ok(Self {
255            killed_by_call,
256            min_size,
257            max_size,
258            alignment,
259            meta_type,
260            extension,
261            operand,
262        })
263    }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
267pub struct Prototype {
268    pub(crate) name: String,
269    pub(crate) extra_pop: u64,
270    pub(crate) stack_shift: u64,
271    pub(crate) inputs: Vec<PrototypeEntry>,
272    pub(crate) outputs: Vec<PrototypeEntry>,
273    pub(crate) unaffected: Vec<PrototypeOperand>,
274    pub(crate) killed_by_call: Vec<PrototypeOperand>,
275    pub(crate) likely_trashed: Vec<PrototypeOperand>,
276}
277
278impl Prototype {
279    pub fn from_xml(input: xml::Node) -> Result<Self, DeserialiseError> {
280        if input.tag_name().name() != "prototype" {
281            return Err(DeserialiseError::TagUnexpected(
282                input.tag_name().name().to_owned(),
283            ));
284        }
285
286        let name = input.attribute_string("name")?;
287        let extra_pop = if matches!(input.attribute("extrapop"), Some("unknown")) {
288            0
289        } else {
290            input.attribute_int("extrapop")?
291        };
292        let stack_shift = input.attribute_int("stackshift")?;
293
294        let mut inputs = Vec::new();
295        let mut outputs = Vec::new();
296        let mut unaffected = Vec::new();
297        let mut killed_by_call = Vec::new();
298        let mut likely_trashed = Vec::new();
299
300        for child in input.children().filter(xml::Node::is_element) {
301            match child.tag_name().name() {
302                "input" => {
303                    let mut values = child
304                        .children()
305                        .filter(xml::Node::is_element)
306                        .map(|v| PrototypeEntry::from_xml(v, false))
307                        .collect::<Result<Vec<_>, _>>()?;
308                    inputs.append(&mut values);
309                }
310                "output" => {
311                    let killed = child.attribute_bool("killedbycall").unwrap_or(false);
312                    let mut values = child
313                        .children()
314                        .filter(xml::Node::is_element)
315                        .map(|v| PrototypeEntry::from_xml(v, killed))
316                        .collect::<Result<Vec<_>, _>>()?;
317                    outputs.append(&mut values);
318                }
319                "unaffected" => {
320                    let mut values = child
321                        .children()
322                        .filter(xml::Node::is_element)
323                        .filter_map(|op| PrototypeOperand::from_xml(op).ok())
324                        .collect::<Vec<_>>();
325                    unaffected.append(&mut values);
326                }
327                "killedbycall" => {
328                    let mut values = child
329                        .children()
330                        .filter(xml::Node::is_element)
331                        .filter_map(|op| PrototypeOperand::from_xml(op).ok())
332                        .collect::<Vec<_>>();
333                    killed_by_call.append(&mut values);
334                }
335                "likelytrash" => {
336                    let mut values = child
337                        .children()
338                        .filter(xml::Node::is_element)
339                        .filter_map(|op| PrototypeOperand::from_xml(op).ok())
340                        .collect::<Vec<_>>();
341                    likely_trashed.append(&mut values);
342                }
343                _ => (),
344            }
345        }
346
347        Ok(Self {
348            name,
349            extra_pop,
350            stack_shift,
351            inputs,
352            outputs,
353            unaffected,
354            killed_by_call,
355            likely_trashed,
356        })
357    }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
361pub struct Specification {
362    pub(crate) name: String,
363    pub(crate) data_organisation: Option<DataOrganisation>,
364    pub(crate) stack_pointer: StackPointer,
365    pub(crate) return_address: ReturnAddress,
366    pub(crate) default_prototype: Prototype,
367    pub(crate) additional_prototypes: Vec<Prototype>,
368    pub(crate) call_fixups: Vec<CallFixup>,
369}
370
371impl Specification {
372    pub fn named_from_xml<N: Into<String>>(
373        name: N,
374        input: xml::Node,
375    ) -> Result<Self, DeserialiseError> {
376        if input.tag_name().name() != "compiler_spec" {
377            return Err(DeserialiseError::TagUnexpected(
378                input.tag_name().name().to_owned(),
379            ));
380        }
381
382        let mut data_organisation = None;
383        let mut stack_pointer = None;
384        let mut return_address = None;
385        let mut default_prototype = None;
386        let mut additional_prototypes = Vec::new();
387        let mut call_fixups = Vec::new();
388
389        for child in input.children().filter(xml::Node::is_element) {
390            match child.tag_name().name() {
391                "data_organization" => {
392                    data_organisation = Some(DataOrganisation::from_xml(child)?);
393                }
394                "stackpointer" => {
395                    stack_pointer = Some(StackPointer::from_xml(child)?);
396                }
397                "returnaddress" => {
398                    return_address = Some(ReturnAddress::from_xml(child)?);
399                }
400                "default_proto" => {
401                    let proto = child.children().filter(xml::Node::is_element).next();
402                    if proto.is_none() {
403                        return Err(DeserialiseError::Invariant(
404                                "compiler specification does not define prototype for default prototype"
405                        ));
406                    }
407                    default_prototype = Some(Prototype::from_xml(proto.unwrap())?);
408                }
409                "prototype" => {
410                    additional_prototypes.push(Prototype::from_xml(child)?);
411                }
412                "callfixup" => {
413                    call_fixups.push(CallFixup::from_xml(child)?);
414                }
415                _ => (),
416            }
417        }
418
419        // if data_organisation.is_none() {
420        //     return Err(DeserialiseError::Invariant(
421        //             "compiler specification does not define data organisation"
422        //     ))
423        // }
424
425        if stack_pointer.is_none() {
426            return Err(DeserialiseError::Invariant(
427                "compiler specification does not define stack pointer configuration",
428            ));
429        }
430
431        if return_address.is_none() {
432            return Err(DeserialiseError::Invariant(
433                "compiler specification does not define return address",
434            ));
435        }
436
437        Ok(Self {
438            name: name.into(),
439            data_organisation,
440            stack_pointer: stack_pointer.unwrap(),
441            return_address: return_address.unwrap(),
442            default_prototype: default_prototype.unwrap(),
443            additional_prototypes,
444            call_fixups,
445        })
446    }
447
448    pub fn named_from_file<N: Into<String>, P: AsRef<Path>>(
449        name: N,
450        path: P,
451    ) -> Result<Self, Error> {
452        let path = path.as_ref();
453        let mut file = File::open(path).map_err(|error| Error::ParseFile {
454            path: path.to_owned(),
455            error,
456        })?;
457
458        let mut input = String::new();
459        file.read_to_string(&mut input)
460            .map_err(|error| Error::ParseFile {
461                path: path.to_owned(),
462                error,
463            })?;
464
465        Self::named_from_str(name, &input).map_err(|error| Error::DeserialiseFile {
466            path: path.to_owned(),
467            error,
468        })
469    }
470
471    pub fn named_from_str<N: Into<String>, S: AsRef<str>>(
472        name: N,
473        input: S,
474    ) -> Result<Self, DeserialiseError> {
475        let document = xml::Document::parse(input.as_ref()).map_err(DeserialiseError::Xml)?;
476
477        let res = Self::named_from_xml(name, document.root_element());
478
479        if let Err(ref e) = res {
480            log::debug!("load failed: {:?}", e);
481        }
482
483        res
484    }
485}
486
487#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
488pub struct CallFixup {
489    name: String,
490    shift: i64,
491    targets: UstrSet,
492    pcode: String,
493}
494
495impl CallFixup {
496    pub fn name(&self) -> &str {
497        &self.name
498    }
499
500    pub fn targets(&self) -> &UstrSet {
501        &self.targets
502    }
503
504    pub fn pcode(&self) -> &str {
505        &self.pcode
506    }
507
508    pub fn shift(&self) -> i64 {
509        self.shift
510    }
511}
512
513impl CallFixup {
514    pub fn from_xml(input: xml::Node) -> Result<Self, DeserialiseError> {
515        if input.tag_name().name() != "callfixup" {
516            return Err(DeserialiseError::TagUnexpected(
517                input.tag_name().name().to_owned(),
518            ));
519        }
520
521        let name = input.attribute_string("name")?;
522
523        let mut targets = UstrSet::default();
524        let mut pcode = None;
525        let mut shift = 0;
526
527        for child in input.children().filter(xml::Node::is_element) {
528            match child.tag_name().name() {
529                "target" => {
530                    let name = child.attribute_string("name")?;
531                    targets.insert(name.into());
532                }
533                "pcode" => {
534                    if pcode.is_some() {
535                        return Err(DeserialiseError::Invariant(
536                            "call fixup has multiple bodies",
537                        ));
538                    }
539
540                    // first child should be pcode
541                    let Some(elt) = child.first_element_child() else {
542                        return Err(DeserialiseError::Invariant(
543                            "call fixup body does not contain any injectable pcode",
544                        ));
545                    };
546
547                    if elt.tag_name().name() != "body" {
548                        return Err(DeserialiseError::TagUnexpected(
549                            elt.tag_name().name().to_owned(),
550                        ));
551                    }
552
553                    if let Some(text) = elt.text().map(ToOwned::to_owned) {
554                        shift = child.attribute_int_opt("paramshift", 0i64)?;
555                        pcode = Some(text);
556                    }
557                }
558                _ => (),
559            }
560        }
561
562        if pcode.is_none() {
563            return Err(DeserialiseError::Invariant(
564                "call fixup does not define any injectable pcode",
565            ));
566        }
567
568        Ok(Self {
569            name,
570            targets,
571            shift,
572            pcode: pcode.unwrap(),
573        })
574    }
575}