Skip to main content

firmion_ir/
ir.rs

1// Shared intermediate representation (IR) types for firmion.
2//
3// This crate defines the data types that flow between the lineardb, irdb and
4// engine pipeline stages.  IRKind enumerates every operation the compiler
5// understands.  ParameterValue holds a typed runtime value (U64, I64, Integer,
6// QuotedString, or Identifier) for each operand.  IROperand pairs a
7// ParameterValue with its source location and its optional back-reference to
8// the IR instruction that produced it.  IR bundles a kind, a source location,
9// and a list of operand indices into a single instruction record.
10//
11// Order of operations: ir.rs is a shared library with no pipeline logic of
12// its own.  lineardb populates LinIR records, irdb converts them into typed IR
13// and IROperand values, and engine reads those values during iteration and
14// execution.
15
16// Don't clutter upstream docs.rs for an otherwise private library.
17#![doc(hidden)]
18
19pub mod symtable;
20
21use diags::Diags;
22use diags::SourceSpan;
23use parse_int::parse;
24
25/// Resolved properties of an `obj` declaration.
26/// `file` and `name` are glob patterns; empty `file_exclude`/`section_exclude`
27/// means no exclusion.  A literal path with no wildcards behaves identically
28/// to the pre-glob single-file/single-section form.
29#[derive(Clone, Debug)]
30pub struct ObjProps {
31    pub file: String,
32    pub name: String,
33    pub file_exclude: String,
34    pub section_exclude: String,
35    /// Source location of the `obj` declaration, for two-location diagnostics.
36    pub src_loc: SourceSpan,
37}
38
39/// Resolved metadata for one `obj` declaration, extracted from the object file.
40/// Produced by the `objfile` crate during IRDb construction; consumed by
41/// layout_phase and exec_phase.
42#[derive(Clone, Debug)]
43pub struct ObjsecInfo {
44    /// Path to the object file.
45    pub file: String,
46    /// Name of the section within the object file.
47    pub name: String,
48    pub file_offset: u64,
49    pub size: u64,
50    /// Required alignment of the section as declared in the object file.
51    pub align: u64,
52    /// Virtual memory address of the section.
53    pub vma: u64,
54    /// Load memory address of the section.  Equals VMA for non-ELF formats.
55    pub lma: u64,
56    pub src_loc: SourceSpan,
57}
58
59/// Region properties bound to a section via `section NAME in REGION`.
60/// Stored on IRDb; consumed by LayoutPhase and later execution phases.
61/// Carries the region name and declaration source location so that every
62/// error site can report which region was violated without extra lookups.
63#[derive(Clone, Debug)]
64pub struct RegionProps {
65    pub addr: u64,
66    pub size: u64,
67    /// The region name as written in source, e.g. "FLASH".
68    pub name: String,
69    pub default_pad_byte: u8,
70    /// Source location of the region declaration for diagnostic labels.
71    pub src_loc: SourceSpan,
72}
73
74impl RegionProps {
75    /// Returns the intersection of self and other, or None when disjoint.
76    /// The intersection name is "{self} & {other}" for diagnostics.
77    pub fn intersect(&self, other: &RegionProps) -> Option<RegionProps> {
78        let addr = self.addr.max(other.addr);
79        // No overflow, bare addition is safe.
80        let end = (self.addr + self.size).min(other.addr + other.size);
81        if end <= addr {
82            return None;
83        }
84        Some(RegionProps {
85            addr,
86            size: end - addr,
87            name: format!("{} & {}", self.name, other.name),
88            default_pad_byte: other.default_pad_byte,
89            src_loc: other.src_loc.clone(),
90        })
91    }
92}
93
94/// The effective region constraint for a section: the geometric intersection of
95/// all ancestor region bindings plus the section's own direct binding.
96/// region_stack holds each RegionProps that narrowed the intersection,
97/// outermost first, for use in ERR_186 backtrace diagnostics.
98#[derive(Clone, Debug)]
99pub struct EffectiveRegion {
100    /// Intersection of all regions in the region stack..
101    pub effective_region: RegionProps,
102    /// All applicable regions, outermost first.
103    pub region_stack: Vec<RegionProps>,
104}
105
106impl EffectiveRegion {
107    /// Returns true when addr falls within [effective_region.addr,
108    /// effective_region.addr + size). No overflow: RegionDb validates addr +
109    /// size before constructing EffectiveRegion.
110    pub fn contains_addr(&self, addr: u64) -> bool {
111        let b = &self.effective_region;
112        addr >= b.addr && addr < b.addr + b.size
113    }
114
115    /// Returns true when sec_size fits within the effective region. On failure,
116    /// emits an error with per-contributor labels when region_stack is
117    /// non-empty, or a two-location error otherwise.
118    pub fn check_section_fits(
119        &self,
120        sec_name: &str,
121        sec_size: u64,
122        use_src_loc: SourceSpan,
123        diags: &mut Diags,
124    ) -> bool {
125        let b = &self.effective_region;
126        if sec_size <= b.size {
127            return true;
128        }
129        let excess = sec_size - b.size;
130        let msg = format!(
131            "Section '{}' size {} bytes exceeds region '{}' effective size {} by {} bytes.",
132            sec_name, sec_size, b.name, b.size, excess
133        );
134        let secondaries: Vec<(SourceSpan, String)> = if !self.region_stack.is_empty() {
135            self.region_stack
136                .iter()
137                .map(|c| {
138                    (
139                        c.src_loc.clone(),
140                        format!("region '{}': addr={:#X}, size={}", c.name, c.addr, c.size),
141                    )
142                })
143                .collect()
144        } else {
145            vec![(
146                b.src_loc.clone(),
147                format!("region '{}': addr={:#X}, size={}", b.name, b.addr, b.size),
148            )]
149        };
150        diags.err_with_locs("ERR_186", &msg, use_src_loc, &secondaries);
151        false
152    }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum IRKind {
157    Addr,
158    Add,
159    Align,
160    Assert,
161    BitAnd,
162    BitOr,
163    Const,
164    Divide,
165    DoubleEq,
166    Eq,
167    /// Declares a const with no value: `const NAME;`.
168    /// Operands: [name_identifier]
169    ConstDeclare,
170    /// Marks the start of an `if` block.
171    /// Operands: [condition_expr_output]
172    IfBegin,
173    /// Marks the transition from the then-body to the else-body.
174    /// Operands: []
175    ElseBegin,
176    /// Marks the end of an if/else construct.
177    /// Operands: []
178    IfEnd,
179    /// Bare assignment inside an if/else body: `NAME = expr;`
180    /// Operands: [name_identifier, rhs_expr_output]
181    BareAssign,
182    /// Operands: [name, arg0..., output]
183    /// All extension calls use this form.  The engine resolves Slice-kinded
184    /// params to ParamArg::Slice by consulting cached_params in the registry.
185    ExtensionCall,
186    GEq,
187    Gt,
188    I64,
189    AddrOffset,
190    Label,
191    LeftShift,
192    LEq,
193    Lt,
194    LogicalAnd,
195    LogicalOr,
196    Modulo,
197    Multiply,
198    NEq,
199    BuiltinOutputAddr,
200    BuiltinOutputSize,
201    BuiltinVersionMajor,
202    BuiltinVersionMinor,
203    BuiltinVersionPatch,
204    BuiltinVersionString,
205    SetSecOffset,
206    SetAddrOffset,
207    SetAddr,
208    SetFileOffset,
209    Print,
210    Trace,
211    RightShift,
212    SecOffset,
213    FileOffset,
214    SectionEnd,
215    SectionStart,
216    ObjAlign,
217    ObjLma,
218    ObjVma,
219    Sizeof,
220    SizeofExt,
221    Subtract,
222    ToI64,
223    ToU64,
224    U64,
225    /// Write N=1 to 8 bytes. Bool true = big-endian, otherwise little-endian.
226    Wr(u8, bool),
227    Wrf,
228    Wrobj,
229    Wrs,
230    /// Sentinel marking the boundary between pre-output and post-output IR.
231    /// Instructions before this point contribute to the output file.
232    /// Instructions after this point execute after the file is written.
233    /// Carries no operands and produces no bytes.
234    Output,
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum DataType {
239    U64,
240    I64,
241    Integer, // ambiguously U64 or I64
242    QuotedString,
243    /// A name whose string is the final value.  Never substituted.
244    Identifier,
245    /// An identifier that refers to a concrete type not yet known at
246    /// linearization time.  The layout engine resolves DeferredRef.
247    DeferredRef,
248    /// Output type of an extension call.  All type checks reject Extension
249    /// except for the ExtensionCall/ExtensionCallSection IR output slot.
250    Extension,
251    /// An operand that failed to convert to a known type.  Eventually
252    /// becomes an error.
253    Unknown,
254}
255
256#[derive(Debug, Clone, PartialEq)]
257pub enum ParameterValue {
258    U64(u64),
259    I64(i64),
260    Integer(i64), // ambiguously U64 or I64, physically backed by i64
261    QuotedString(String),
262    /// An identifier whose string is the final value.  Never substituted.
263    Identifier(String),
264    /// An identifier that refers to a concrete type not yet known at
265    /// linearization time.  The layout engine resolves DeferredRef.
266    DeferredRef(String),
267    /// Placeholder value for the output slot of an extension call.
268    Extension,
269    /// An operand that failed to convert to a known type.  Eventually
270    /// becomes an error.
271    Unknown,
272}
273
274impl ParameterValue {
275    // Some operations require numeric operands, so provide a concise check.
276    pub fn is_numeric(&self) -> bool {
277        matches!(
278            self,
279            ParameterValue::U64(_) | ParameterValue::I64(_) | ParameterValue::Integer(_)
280        )
281    }
282
283    pub fn data_type(&self) -> DataType {
284        match self {
285            ParameterValue::U64(_) => DataType::U64,
286            ParameterValue::I64(_) => DataType::I64,
287            ParameterValue::Integer(_) => DataType::Integer,
288            ParameterValue::QuotedString(_) => DataType::QuotedString,
289            ParameterValue::Identifier(_) => DataType::Identifier,
290            ParameterValue::DeferredRef(_) => DataType::DeferredRef,
291            ParameterValue::Extension => DataType::Extension,
292            ParameterValue::Unknown => DataType::Unknown,
293        }
294    }
295
296    pub fn to_bool(&self) -> Option<bool> {
297        match self {
298            ParameterValue::I64(v) | ParameterValue::Integer(v) => Some((*v as u64) != 0),
299            ParameterValue::U64(v) => Some(*v != 0),
300            _ => None,
301        }
302    }
303
304    pub fn to_u64(&self) -> u64 {
305        match self {
306            ParameterValue::I64(v) | ParameterValue::Integer(v) => *v as u64,
307            ParameterValue::U64(v) => *v,
308            _ => {
309                panic!("Internal error: Invalid type conversion from {:?} to u64", self);
310            }
311        }
312    }
313
314    pub fn to_u64_mut(&mut self) -> &mut u64 {
315        match self {
316            ParameterValue::U64(v) => v,
317            _ => {
318                panic!("Internal error: Invalid type conversion from {:?} to &mut u64", self);
319            }
320        }
321    }
322
323    pub fn to_i64(&self) -> i64 {
324        match self {
325            ParameterValue::I64(v) | ParameterValue::Integer(v) => *v,
326            _ => {
327                panic!("Internal error: Invalid type conversion from {:?} to i64", self);
328            }
329        }
330    }
331
332    pub fn to_i64_mut(&mut self) -> &mut i64 {
333        match self {
334            ParameterValue::I64(v) | ParameterValue::Integer(v) => v,
335            _ => {
336                panic!("Internal error: Invalid type conversion from {:?} to &mut i64", self);
337            }
338        }
339    }
340
341    pub fn to_str(&self) -> &str {
342        match self {
343            ParameterValue::QuotedString(s) => s,
344            _ => {
345                panic!("Internal error: Invalid type conversion from {:?} to str", self);
346            }
347        }
348    }
349
350    pub fn identifier_to_str(&self) -> &str {
351        match self {
352            ParameterValue::Identifier(s) | ParameterValue::DeferredRef(s) => s,
353            _ => {
354                panic!("Internal error: Invalid type conversion from {:?} to identifier", self);
355            }
356        }
357    }
358}
359
360/// Strip a trailing K/M/G magnitude suffix from a numeric string.
361/// Returns the stripped string and the magnitude multiplier.
362pub fn strip_kmg(s: &str) -> (&str, u64) {
363    match s.as_bytes().last() {
364        Some(b'K') => (&s[..s.len() - 1], 1024),
365        Some(b'M') => (&s[..s.len() - 1], 1024 * 1024),
366        Some(b'G') => (&s[..s.len() - 1], 1024 * 1024 * 1024),
367        _ => (s, 1),
368    }
369}
370
371#[derive(Debug)]
372pub struct IROperand {
373    /// The linear ID of the IR instruction whose output this operand carries,
374    /// or None if this is an immediate (literal) operand with no producing
375    /// instruction.
376    pub ir_lid: Option<usize>,
377    /// Byte range in the source file that produced this operand, used for
378    /// error reporting.
379    pub src_loc: SourceSpan,
380    /// True if this operand holds a literal value parsed directly from source
381    /// (e.g. a numeric constant or quoted string).  False if this is the
382    /// output placeholder of an IR instruction whose value is computed at
383    /// engine time.
384    pub is_immediate: bool,
385    /// The typed runtime value of this operand.  For immediate operands this
386    /// is parsed from the source literal; for output placeholders it is
387    /// initialized to a zero-equivalent and overwritten during execution.
388    pub val: ParameterValue,
389    /// Named-argument parameter name from the call site, if the caller used
390    /// `name=value` syntax.  None for positional arguments.
391    pub param_name: Option<String>,
392}
393
394impl IROperand {
395    pub fn new(
396        ir_lid: Option<usize>,
397        sval: &str,
398        src_loc: &SourceSpan,
399        data_type: DataType,
400        is_immediate: bool,
401        diags: &mut Diags,
402    ) -> Option<IROperand> {
403        if let Some(val) = IROperand::convert_type(sval, data_type, src_loc, is_immediate, diags) {
404            return Some(IROperand {
405                ir_lid,
406                src_loc: src_loc.clone(),
407                is_immediate,
408                val,
409                param_name: None,
410            });
411        }
412
413        None
414    }
415
416    pub fn is_output_of(&self) -> Option<usize> {
417        self.ir_lid
418    }
419
420    /// Converts the specified string into the specified type
421    fn convert_type(
422        sval: &str,
423        data_type: DataType,
424        src_loc: &SourceSpan,
425        is_immediate: bool,
426        diags: &mut Diags,
427    ) -> Option<ParameterValue> {
428        match data_type {
429            DataType::QuotedString => {
430                if !is_immediate {
431                    // Output operand of a string-typed Const IR.  The resolved
432                    // value is stored in const_values and substituted before
433                    // this placeholder is ever read, so an empty string is fine.
434                    return Some(ParameterValue::QuotedString(String::new()));
435                }
436                // Trim quotes and convert escape characters
437                // For trimming, don't use trim_matches since that
438                // will incorrectly strip trailing escaped quotes.
439                return Some(ParameterValue::QuotedString(
440                    sval.strip_prefix('\"')
441                        .unwrap()
442                        .strip_suffix('\"')
443                        .unwrap()
444                        .replace("\\\"", "\"")
445                        .replace("\\n", "\n")
446                        .replace("\\0", "\0")
447                        .replace("\\t", "\t"),
448                ));
449            }
450            DataType::Extension => {
451                return Some(ParameterValue::Extension);
452            }
453            DataType::U64 => {
454                if is_immediate {
455                    let sval_no_u = sval.strip_suffix('u').unwrap_or(sval);
456                    let (sval_base, mult) = strip_kmg(sval_no_u);
457                    match parse::<u64>(sval_base).ok().and_then(|v| v.checked_mul(mult)) {
458                        Some(v) => return Some(ParameterValue::U64(v)),
459                        None => {
460                            let m = format!("Malformed integer operand {}", sval);
461                            diags.err1("ERR_198", &m, src_loc.clone());
462                        }
463                    }
464                } else {
465                    // We don't know variable value, so initialize to zero
466                    return Some(ParameterValue::U64(0));
467                }
468            }
469
470            DataType::I64 => {
471                if is_immediate {
472                    let sval_no_i = sval.strip_suffix('i').unwrap_or(sval);
473                    let (sval_base, mult) = strip_kmg(sval_no_i);
474                    match parse::<i64>(sval_base).ok().and_then(|v| v.checked_mul(mult as i64)) {
475                        Some(v) => return Some(ParameterValue::I64(v)),
476                        None => {
477                            let m = format!("Malformed integer operand {}", sval);
478                            diags.err1("ERR_200", &m, src_loc.clone());
479                        }
480                    }
481                } else {
482                    // We don't know variable value, so initialize to zero
483                    return Some(ParameterValue::I64(0));
484                }
485            }
486
487            DataType::Integer => {
488                if is_immediate {
489                    // Store as i64: expectations like 1 - 2 == -1 hold.
490                    let (sval_base, mult) = strip_kmg(sval);
491                    match parse::<i64>(sval_base).ok().and_then(|v| v.checked_mul(mult as i64)) {
492                        Some(v) => return Some(ParameterValue::Integer(v)),
493                        None => {
494                            let m = format!("Malformed integer operand {}", sval);
495                            diags.err1("ERR_201", &m, src_loc.clone());
496                        }
497                    }
498                } else {
499                    // We don't know variable value, so initialize to zero
500                    return Some(ParameterValue::Integer(0));
501                }
502            }
503
504            DataType::Identifier => {
505                return Some(ParameterValue::Identifier(sval.to_string()));
506            }
507            DataType::DeferredRef => {
508                return Some(ParameterValue::DeferredRef(sval.to_string()));
509            }
510            DataType::Unknown => {
511                let m = format!("Conversion failed for unknown type {}.", sval);
512                diags.err1("ERR_199", &m, src_loc.clone());
513                return Some(ParameterValue::Unknown);
514            }
515        };
516        None
517    }
518
519    pub fn clone_val(&self) -> ParameterValue {
520        self.val.clone()
521    }
522}
523
524#[derive(Debug, Clone)]
525pub struct IR {
526    pub kind: IRKind,
527    pub operands: Vec<usize>,
528    pub src_loc: SourceSpan,
529}
530
531/// All compile-time constants exposed as Firmion built-in variables.
532///
533/// Call `ConstBuiltins::init()` once at process startup before any built-in
534/// variable is accessed.  Add new compile-time builtins as fields here as the
535/// language grows.
536pub struct ConstBuiltins {
537    pub firmion_version_string: &'static str,
538    pub firmion_version_major: u64,
539    pub firmion_version_minor: u64,
540    pub firmion_version_patch: u64,
541}
542
543impl ConstBuiltins {
544    /// Initialize all compile-time built-in constants.
545    /// This is deterministic and can be called multiple times safely.
546    pub fn init() {
547        // No-op. initialization is deterministic and done at each get().
548    }
549
550    pub fn from_version_str(version: &'static str) -> Self {
551        let mut parts = version.splitn(3, '.');
552        ConstBuiltins {
553            firmion_version_string: version,
554            firmion_version_major: parts.next().and_then(|s| s.parse().ok()).unwrap_or(0),
555            firmion_version_minor: parts.next().and_then(|s| s.parse().ok()).unwrap_or(0),
556            firmion_version_patch: parts.next().and_then(|s| s.parse().ok()).unwrap_or(0),
557        }
558    }
559
560    pub fn get() -> Self {
561        Self::from_version_str(env!("CARGO_PKG_VERSION"))
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use super::ConstBuiltins;
568
569    #[test]
570    fn parse_version_string_valid() {
571        let b = ConstBuiltins::from_version_str("4.5.6");
572        assert_eq!(b.firmion_version_string, "4.5.6");
573        assert_eq!(b.firmion_version_major, 4);
574        assert_eq!(b.firmion_version_minor, 5);
575        assert_eq!(b.firmion_version_patch, 6);
576    }
577
578    #[test]
579    fn parse_version_string_malformed_minor() {
580        let b = ConstBuiltins::from_version_str("10.x");
581        assert_eq!(b.firmion_version_string, "10.x");
582        assert_eq!(b.firmion_version_major, 10);
583        assert_eq!(b.firmion_version_minor, 0);
584        assert_eq!(b.firmion_version_patch, 0);
585    }
586
587    #[test]
588    fn parse_version_string_partial_only() {
589        let b = ConstBuiltins::from_version_str("7");
590        assert_eq!(b.firmion_version_string, "7");
591        assert_eq!(b.firmion_version_major, 7);
592        assert_eq!(b.firmion_version_minor, 0);
593        assert_eq!(b.firmion_version_patch, 0);
594    }
595
596    #[test]
597    fn parse_version_string_nonnumeric() {
598        let b = ConstBuiltins::from_version_str("foo.bar.baz");
599        assert_eq!(b.firmion_version_string, "foo.bar.baz");
600        assert_eq!(b.firmion_version_major, 0);
601        assert_eq!(b.firmion_version_minor, 0);
602        assert_eq!(b.firmion_version_patch, 0);
603    }
604}