Skip to main content

nwnrs_nwscript/
codegen.rs

1use std::{
2    collections::BTreeMap,
3    error::Error,
4    fmt,
5    time::{Duration, SystemTime, UNIX_EPOCH},
6};
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    AssignmentOp, BinaryOp, BuiltinFunction, BuiltinType, BuiltinValue, HirBlock, HirCallTarget,
12    HirExpr, HirExprKind, HirFunction, HirLocalId, HirLocalKind, HirModule, HirStmt, LangSpec,
13    Literal, NCS_OPERATION_BASE_SIZE, NcsAuxCode, NcsInstruction, NcsOpcode, Ndb, NdbFile,
14    NdbFunction, NdbLine, NdbStruct, NdbStructField, NdbType, NdbVariable, Script, SemanticOptions,
15    SemanticType, SourceBundle, SourceId, SourceMap, UnaryOp, analyze_script_with_options,
16    encode_ncs_instructions, lower_to_hir, nwscript_string_hash,
17    opt::{
18        ConstValue, build_constant_env, evaluate_const_expr, meld_instructions,
19        optimization_needs_hir_passes, optimization_needs_post_codegen_passes, optimize_hir,
20    },
21    parse_source_bundle, parse_text, write_ndb,
22};
23
24/// Optimization levels accepted by the pure-Rust compiler pipeline.
25#[derive(#[automatically_derived]
impl ::core::fmt::Debug for OptimizationLevel {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                OptimizationLevel::O0 => "O0",
                OptimizationLevel::O1 => "O1",
                OptimizationLevel::O2 => "O2",
                OptimizationLevel::O3 => "O3",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for OptimizationLevel {
    #[inline]
    fn clone(&self) -> OptimizationLevel { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for OptimizationLevel { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for OptimizationLevel {
    #[inline]
    fn eq(&self, other: &OptimizationLevel) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for OptimizationLevel {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq, #[automatically_derived]
impl ::core::hash::Hash for OptimizationLevel {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        ::core::hash::Hash::hash(&__self_discr, state)
    }
}Hash, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl _serde::Serialize for OptimizationLevel {
            fn serialize<__S>(&self, __serializer: __S)
                -> _serde::__private228::Result<__S::Ok, __S::Error> where
                __S: _serde::Serializer {
                match *self {
                    OptimizationLevel::O0 =>
                        _serde::Serializer::serialize_unit_variant(__serializer,
                            "OptimizationLevel", 0u32, "O0"),
                    OptimizationLevel::O1 =>
                        _serde::Serializer::serialize_unit_variant(__serializer,
                            "OptimizationLevel", 1u32, "O1"),
                    OptimizationLevel::O2 =>
                        _serde::Serializer::serialize_unit_variant(__serializer,
                            "OptimizationLevel", 2u32, "O2"),
                    OptimizationLevel::O3 =>
                        _serde::Serializer::serialize_unit_variant(__serializer,
                            "OptimizationLevel", 3u32, "O3"),
                }
            }
        }
    };Serialize, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl<'de> _serde::Deserialize<'de> for OptimizationLevel {
            fn deserialize<__D>(__deserializer: __D)
                -> _serde::__private228::Result<Self, __D::Error> where
                __D: _serde::Deserializer<'de> {
                #[allow(non_camel_case_types)]
                #[doc(hidden)]
                enum __Field { __field0, __field1, __field2, __field3, }
                #[doc(hidden)]
                struct __FieldVisitor;
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
                    type Value = __Field;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "variant identifier")
                    }
                    fn visit_u64<__E>(self, __value: u64)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            0u64 => _serde::__private228::Ok(__Field::__field0),
                            1u64 => _serde::__private228::Ok(__Field::__field1),
                            2u64 => _serde::__private228::Ok(__Field::__field2),
                            3u64 => _serde::__private228::Ok(__Field::__field3),
                            _ =>
                                _serde::__private228::Err(_serde::de::Error::invalid_value(_serde::de::Unexpected::Unsigned(__value),
                                        &"variant index 0 <= i < 4")),
                        }
                    }
                    fn visit_str<__E>(self, __value: &str)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            "O0" => _serde::__private228::Ok(__Field::__field0),
                            "O1" => _serde::__private228::Ok(__Field::__field1),
                            "O2" => _serde::__private228::Ok(__Field::__field2),
                            "O3" => _serde::__private228::Ok(__Field::__field3),
                            _ => {
                                _serde::__private228::Err(_serde::de::Error::unknown_variant(__value,
                                        VARIANTS))
                            }
                        }
                    }
                    fn visit_bytes<__E>(self, __value: &[u8])
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            b"O0" => _serde::__private228::Ok(__Field::__field0),
                            b"O1" => _serde::__private228::Ok(__Field::__field1),
                            b"O2" => _serde::__private228::Ok(__Field::__field2),
                            b"O3" => _serde::__private228::Ok(__Field::__field3),
                            _ => {
                                let __value =
                                    &_serde::__private228::from_utf8_lossy(__value);
                                _serde::__private228::Err(_serde::de::Error::unknown_variant(__value,
                                        VARIANTS))
                            }
                        }
                    }
                }
                #[automatically_derived]
                impl<'de> _serde::Deserialize<'de> for __Field {
                    #[inline]
                    fn deserialize<__D>(__deserializer: __D)
                        -> _serde::__private228::Result<Self, __D::Error> where
                        __D: _serde::Deserializer<'de> {
                        _serde::Deserializer::deserialize_identifier(__deserializer,
                            __FieldVisitor)
                    }
                }
                #[doc(hidden)]
                struct __Visitor<'de> {
                    marker: _serde::__private228::PhantomData<OptimizationLevel>,
                    lifetime: _serde::__private228::PhantomData<&'de ()>,
                }
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
                    type Value = OptimizationLevel;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "enum OptimizationLevel")
                    }
                    fn visit_enum<__A>(self, __data: __A)
                        -> _serde::__private228::Result<Self::Value, __A::Error>
                        where __A: _serde::de::EnumAccess<'de> {
                        match _serde::de::EnumAccess::variant(__data)? {
                            (__Field::__field0, __variant) => {
                                _serde::de::VariantAccess::unit_variant(__variant)?;
                                _serde::__private228::Ok(OptimizationLevel::O0)
                            }
                            (__Field::__field1, __variant) => {
                                _serde::de::VariantAccess::unit_variant(__variant)?;
                                _serde::__private228::Ok(OptimizationLevel::O1)
                            }
                            (__Field::__field2, __variant) => {
                                _serde::de::VariantAccess::unit_variant(__variant)?;
                                _serde::__private228::Ok(OptimizationLevel::O2)
                            }
                            (__Field::__field3, __variant) => {
                                _serde::de::VariantAccess::unit_variant(__variant)?;
                                _serde::__private228::Ok(OptimizationLevel::O3)
                            }
                        }
                    }
                }
                #[doc(hidden)]
                const VARIANTS: &'static [&'static str] =
                    &["O0", "O1", "O2", "O3"];
                _serde::Deserializer::deserialize_enum(__deserializer,
                    "OptimizationLevel", VARIANTS,
                    __Visitor {
                        marker: _serde::__private228::PhantomData::<OptimizationLevel>,
                        lifetime: _serde::__private228::PhantomData,
                    })
            }
        }
    };Deserialize, #[automatically_derived]
impl ::core::default::Default for OptimizationLevel {
    #[inline]
    fn default() -> OptimizationLevel { Self::O0 }
}Default)]
26pub enum OptimizationLevel {
27    /// Unoptimized code generation.
28    #[default]
29    O0,
30    /// Placeholder for future optimization work.
31    O1,
32    /// Placeholder for future optimization work.
33    O2,
34    /// Placeholder for future optimization work.
35    O3,
36}
37
38/// One compilation request.
39#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CompileOptions {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "CompileOptions", "semantic", &self.semantic, "optimization",
            &&self.optimization)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for CompileOptions {
    #[inline]
    fn clone(&self) -> CompileOptions {
        let _: ::core::clone::AssertParamIsClone<SemanticOptions>;
        let _: ::core::clone::AssertParamIsClone<OptimizationLevel>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for CompileOptions { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for CompileOptions {
    #[inline]
    fn eq(&self, other: &CompileOptions) -> bool {
        self.semantic == other.semantic &&
            self.optimization == other.optimization
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CompileOptions {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<SemanticOptions>;
        let _: ::core::cmp::AssertParamIsEq<OptimizationLevel>;
    }
}Eq, #[automatically_derived]
impl ::core::hash::Hash for CompileOptions {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.semantic, state);
        ::core::hash::Hash::hash(&self.optimization, state)
    }
}Hash, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl _serde::Serialize for CompileOptions {
            fn serialize<__S>(&self, __serializer: __S)
                -> _serde::__private228::Result<__S::Ok, __S::Error> where
                __S: _serde::Serializer {
                let mut __serde_state =
                    _serde::Serializer::serialize_struct(__serializer,
                            "CompileOptions", false as usize + 1 + 1)?;
                _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                        "semantic", &self.semantic)?;
                _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                        "optimization", &self.optimization)?;
                _serde::ser::SerializeStruct::end(__serde_state)
            }
        }
    };Serialize, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl<'de> _serde::Deserialize<'de> for CompileOptions {
            fn deserialize<__D>(__deserializer: __D)
                -> _serde::__private228::Result<Self, __D::Error> where
                __D: _serde::Deserializer<'de> {
                #[allow(non_camel_case_types)]
                #[doc(hidden)]
                enum __Field { __field0, __field1, __ignore, }
                #[doc(hidden)]
                struct __FieldVisitor;
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
                    type Value = __Field;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "field identifier")
                    }
                    fn visit_u64<__E>(self, __value: u64)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            0u64 => _serde::__private228::Ok(__Field::__field0),
                            1u64 => _serde::__private228::Ok(__Field::__field1),
                            _ => _serde::__private228::Ok(__Field::__ignore),
                        }
                    }
                    fn visit_str<__E>(self, __value: &str)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            "semantic" => _serde::__private228::Ok(__Field::__field0),
                            "optimization" =>
                                _serde::__private228::Ok(__Field::__field1),
                            _ => { _serde::__private228::Ok(__Field::__ignore) }
                        }
                    }
                    fn visit_bytes<__E>(self, __value: &[u8])
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            b"semantic" => _serde::__private228::Ok(__Field::__field0),
                            b"optimization" =>
                                _serde::__private228::Ok(__Field::__field1),
                            _ => { _serde::__private228::Ok(__Field::__ignore) }
                        }
                    }
                }
                #[automatically_derived]
                impl<'de> _serde::Deserialize<'de> for __Field {
                    #[inline]
                    fn deserialize<__D>(__deserializer: __D)
                        -> _serde::__private228::Result<Self, __D::Error> where
                        __D: _serde::Deserializer<'de> {
                        _serde::Deserializer::deserialize_identifier(__deserializer,
                            __FieldVisitor)
                    }
                }
                #[doc(hidden)]
                struct __Visitor<'de> {
                    marker: _serde::__private228::PhantomData<CompileOptions>,
                    lifetime: _serde::__private228::PhantomData<&'de ()>,
                }
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
                    type Value = CompileOptions;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "struct CompileOptions")
                    }
                    #[inline]
                    fn visit_seq<__A>(self, mut __seq: __A)
                        -> _serde::__private228::Result<Self::Value, __A::Error>
                        where __A: _serde::de::SeqAccess<'de> {
                        let __field0 =
                            match _serde::de::SeqAccess::next_element::<SemanticOptions>(&mut __seq)?
                                {
                                _serde::__private228::Some(__value) => __value,
                                _serde::__private228::None =>
                                    return _serde::__private228::Err(_serde::de::Error::invalid_length(0usize,
                                                &"struct CompileOptions with 2 elements")),
                            };
                        let __field1 =
                            match _serde::de::SeqAccess::next_element::<OptimizationLevel>(&mut __seq)?
                                {
                                _serde::__private228::Some(__value) => __value,
                                _serde::__private228::None =>
                                    return _serde::__private228::Err(_serde::de::Error::invalid_length(1usize,
                                                &"struct CompileOptions with 2 elements")),
                            };
                        _serde::__private228::Ok(CompileOptions {
                                semantic: __field0,
                                optimization: __field1,
                            })
                    }
                    #[inline]
                    fn visit_map<__A>(self, mut __map: __A)
                        -> _serde::__private228::Result<Self::Value, __A::Error>
                        where __A: _serde::de::MapAccess<'de> {
                        let mut __field0:
                                _serde::__private228::Option<SemanticOptions> =
                            _serde::__private228::None;
                        let mut __field1:
                                _serde::__private228::Option<OptimizationLevel> =
                            _serde::__private228::None;
                        while let _serde::__private228::Some(__key) =
                                _serde::de::MapAccess::next_key::<__Field>(&mut __map)? {
                            match __key {
                                __Field::__field0 => {
                                    if _serde::__private228::Option::is_some(&__field0) {
                                        return _serde::__private228::Err(<__A::Error as
                                                        _serde::de::Error>::duplicate_field("semantic"));
                                    }
                                    __field0 =
                                        _serde::__private228::Some(_serde::de::MapAccess::next_value::<SemanticOptions>(&mut __map)?);
                                }
                                __Field::__field1 => {
                                    if _serde::__private228::Option::is_some(&__field1) {
                                        return _serde::__private228::Err(<__A::Error as
                                                        _serde::de::Error>::duplicate_field("optimization"));
                                    }
                                    __field1 =
                                        _serde::__private228::Some(_serde::de::MapAccess::next_value::<OptimizationLevel>(&mut __map)?);
                                }
                                _ => {
                                    let _ =
                                        _serde::de::MapAccess::next_value::<_serde::de::IgnoredAny>(&mut __map)?;
                                }
                            }
                        }
                        let __field0 =
                            match __field0 {
                                _serde::__private228::Some(__field0) => __field0,
                                _serde::__private228::None =>
                                    _serde::__private228::de::missing_field("semantic")?,
                            };
                        let __field1 =
                            match __field1 {
                                _serde::__private228::Some(__field1) => __field1,
                                _serde::__private228::None =>
                                    _serde::__private228::de::missing_field("optimization")?,
                            };
                        _serde::__private228::Ok(CompileOptions {
                                semantic: __field0,
                                optimization: __field1,
                            })
                    }
                }
                #[doc(hidden)]
                const FIELDS: &'static [&'static str] =
                    &["semantic", "optimization"];
                _serde::Deserializer::deserialize_struct(__deserializer,
                    "CompileOptions", FIELDS,
                    __Visitor {
                        marker: _serde::__private228::PhantomData::<CompileOptions>,
                        lifetime: _serde::__private228::PhantomData,
                    })
            }
        }
    };Deserialize)]
40pub struct CompileOptions {
41    /// Entry-point validation policy forwarded to semantic analysis.
42    pub semantic:     SemanticOptions,
43    /// Optimization level for code generation.
44    pub optimization: OptimizationLevel,
45}
46
47impl Default for CompileOptions {
48    fn default() -> Self {
49        Self {
50            semantic:     SemanticOptions::default(),
51            optimization: OptimizationLevel::O0,
52        }
53    }
54}
55
56/// Compiler outputs produced in memory.
57#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CompileArtifacts {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "CompileArtifacts", "ncs", &self.ncs, "ndb", &&self.ndb)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for CompileArtifacts {
    #[inline]
    fn clone(&self) -> CompileArtifacts {
        CompileArtifacts {
            ncs: ::core::clone::Clone::clone(&self.ncs),
            ndb: ::core::clone::Clone::clone(&self.ndb),
        }
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for CompileArtifacts {
    #[inline]
    fn eq(&self, other: &CompileArtifacts) -> bool {
        self.ncs == other.ncs && self.ndb == other.ndb
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CompileArtifacts {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Vec<u8>>;
        let _: ::core::cmp::AssertParamIsEq<Option<Vec<u8>>>;
    }
}Eq, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl _serde::Serialize for CompileArtifacts {
            fn serialize<__S>(&self, __serializer: __S)
                -> _serde::__private228::Result<__S::Ok, __S::Error> where
                __S: _serde::Serializer {
                let mut __serde_state =
                    _serde::Serializer::serialize_struct(__serializer,
                            "CompileArtifacts", false as usize + 1 + 1)?;
                _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                        "ncs", &self.ncs)?;
                _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                        "ndb", &self.ndb)?;
                _serde::ser::SerializeStruct::end(__serde_state)
            }
        }
    };Serialize, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl<'de> _serde::Deserialize<'de> for CompileArtifacts {
            fn deserialize<__D>(__deserializer: __D)
                -> _serde::__private228::Result<Self, __D::Error> where
                __D: _serde::Deserializer<'de> {
                #[allow(non_camel_case_types)]
                #[doc(hidden)]
                enum __Field { __field0, __field1, __ignore, }
                #[doc(hidden)]
                struct __FieldVisitor;
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
                    type Value = __Field;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "field identifier")
                    }
                    fn visit_u64<__E>(self, __value: u64)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            0u64 => _serde::__private228::Ok(__Field::__field0),
                            1u64 => _serde::__private228::Ok(__Field::__field1),
                            _ => _serde::__private228::Ok(__Field::__ignore),
                        }
                    }
                    fn visit_str<__E>(self, __value: &str)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            "ncs" => _serde::__private228::Ok(__Field::__field0),
                            "ndb" => _serde::__private228::Ok(__Field::__field1),
                            _ => { _serde::__private228::Ok(__Field::__ignore) }
                        }
                    }
                    fn visit_bytes<__E>(self, __value: &[u8])
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            b"ncs" => _serde::__private228::Ok(__Field::__field0),
                            b"ndb" => _serde::__private228::Ok(__Field::__field1),
                            _ => { _serde::__private228::Ok(__Field::__ignore) }
                        }
                    }
                }
                #[automatically_derived]
                impl<'de> _serde::Deserialize<'de> for __Field {
                    #[inline]
                    fn deserialize<__D>(__deserializer: __D)
                        -> _serde::__private228::Result<Self, __D::Error> where
                        __D: _serde::Deserializer<'de> {
                        _serde::Deserializer::deserialize_identifier(__deserializer,
                            __FieldVisitor)
                    }
                }
                #[doc(hidden)]
                struct __Visitor<'de> {
                    marker: _serde::__private228::PhantomData<CompileArtifacts>,
                    lifetime: _serde::__private228::PhantomData<&'de ()>,
                }
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
                    type Value = CompileArtifacts;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "struct CompileArtifacts")
                    }
                    #[inline]
                    fn visit_seq<__A>(self, mut __seq: __A)
                        -> _serde::__private228::Result<Self::Value, __A::Error>
                        where __A: _serde::de::SeqAccess<'de> {
                        let __field0 =
                            match _serde::de::SeqAccess::next_element::<Vec<u8>>(&mut __seq)?
                                {
                                _serde::__private228::Some(__value) => __value,
                                _serde::__private228::None =>
                                    return _serde::__private228::Err(_serde::de::Error::invalid_length(0usize,
                                                &"struct CompileArtifacts with 2 elements")),
                            };
                        let __field1 =
                            match _serde::de::SeqAccess::next_element::<Option<Vec<u8>>>(&mut __seq)?
                                {
                                _serde::__private228::Some(__value) => __value,
                                _serde::__private228::None =>
                                    return _serde::__private228::Err(_serde::de::Error::invalid_length(1usize,
                                                &"struct CompileArtifacts with 2 elements")),
                            };
                        _serde::__private228::Ok(CompileArtifacts {
                                ncs: __field0,
                                ndb: __field1,
                            })
                    }
                    #[inline]
                    fn visit_map<__A>(self, mut __map: __A)
                        -> _serde::__private228::Result<Self::Value, __A::Error>
                        where __A: _serde::de::MapAccess<'de> {
                        let mut __field0: _serde::__private228::Option<Vec<u8>> =
                            _serde::__private228::None;
                        let mut __field1:
                                _serde::__private228::Option<Option<Vec<u8>>> =
                            _serde::__private228::None;
                        while let _serde::__private228::Some(__key) =
                                _serde::de::MapAccess::next_key::<__Field>(&mut __map)? {
                            match __key {
                                __Field::__field0 => {
                                    if _serde::__private228::Option::is_some(&__field0) {
                                        return _serde::__private228::Err(<__A::Error as
                                                        _serde::de::Error>::duplicate_field("ncs"));
                                    }
                                    __field0 =
                                        _serde::__private228::Some(_serde::de::MapAccess::next_value::<Vec<u8>>(&mut __map)?);
                                }
                                __Field::__field1 => {
                                    if _serde::__private228::Option::is_some(&__field1) {
                                        return _serde::__private228::Err(<__A::Error as
                                                        _serde::de::Error>::duplicate_field("ndb"));
                                    }
                                    __field1 =
                                        _serde::__private228::Some(_serde::de::MapAccess::next_value::<Option<Vec<u8>>>(&mut __map)?);
                                }
                                _ => {
                                    let _ =
                                        _serde::de::MapAccess::next_value::<_serde::de::IgnoredAny>(&mut __map)?;
                                }
                            }
                        }
                        let __field0 =
                            match __field0 {
                                _serde::__private228::Some(__field0) => __field0,
                                _serde::__private228::None =>
                                    _serde::__private228::de::missing_field("ncs")?,
                            };
                        let __field1 =
                            match __field1 {
                                _serde::__private228::Some(__field1) => __field1,
                                _serde::__private228::None =>
                                    _serde::__private228::de::missing_field("ndb")?,
                            };
                        _serde::__private228::Ok(CompileArtifacts {
                                ncs: __field0,
                                ndb: __field1,
                            })
                    }
                }
                #[doc(hidden)]
                const FIELDS: &'static [&'static str] = &["ncs", "ndb"];
                _serde::Deserializer::deserialize_struct(__deserializer,
                    "CompileArtifacts", FIELDS,
                    __Visitor {
                        marker: _serde::__private228::PhantomData::<CompileArtifacts>,
                        lifetime: _serde::__private228::PhantomData,
                    })
            }
        }
    };Deserialize)]
58pub struct CompileArtifacts {
59    /// Encoded `NCS` bytecode.
60    pub ncs: Vec<u8>,
61    /// Encoded `NDB` debug output when available.
62    pub ndb: Option<Vec<u8>>,
63}
64
65/// One pure-Rust code generation failure.
66#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CodegenError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "CodegenError",
            "span", &self.span, "message", &&self.message)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for CodegenError {
    #[inline]
    fn clone(&self) -> CodegenError {
        CodegenError {
            span: ::core::clone::Clone::clone(&self.span),
            message: ::core::clone::Clone::clone(&self.message),
        }
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for CodegenError {
    #[inline]
    fn eq(&self, other: &CodegenError) -> bool {
        self.span == other.span && self.message == other.message
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CodegenError {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Option<crate::Span>>;
        let _: ::core::cmp::AssertParamIsEq<String>;
    }
}Eq, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl _serde::Serialize for CodegenError {
            fn serialize<__S>(&self, __serializer: __S)
                -> _serde::__private228::Result<__S::Ok, __S::Error> where
                __S: _serde::Serializer {
                let mut __serde_state =
                    _serde::Serializer::serialize_struct(__serializer,
                            "CodegenError", false as usize + 1 + 1)?;
                _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                        "span", &self.span)?;
                _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                        "message", &self.message)?;
                _serde::ser::SerializeStruct::end(__serde_state)
            }
        }
    };Serialize, #[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications,
clippy :: absolute_paths,)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        ;
        #[automatically_derived]
        impl<'de> _serde::Deserialize<'de> for CodegenError {
            fn deserialize<__D>(__deserializer: __D)
                -> _serde::__private228::Result<Self, __D::Error> where
                __D: _serde::Deserializer<'de> {
                #[allow(non_camel_case_types)]
                #[doc(hidden)]
                enum __Field { __field0, __field1, __ignore, }
                #[doc(hidden)]
                struct __FieldVisitor;
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
                    type Value = __Field;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "field identifier")
                    }
                    fn visit_u64<__E>(self, __value: u64)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            0u64 => _serde::__private228::Ok(__Field::__field0),
                            1u64 => _serde::__private228::Ok(__Field::__field1),
                            _ => _serde::__private228::Ok(__Field::__ignore),
                        }
                    }
                    fn visit_str<__E>(self, __value: &str)
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            "span" => _serde::__private228::Ok(__Field::__field0),
                            "message" => _serde::__private228::Ok(__Field::__field1),
                            _ => { _serde::__private228::Ok(__Field::__ignore) }
                        }
                    }
                    fn visit_bytes<__E>(self, __value: &[u8])
                        -> _serde::__private228::Result<Self::Value, __E> where
                        __E: _serde::de::Error {
                        match __value {
                            b"span" => _serde::__private228::Ok(__Field::__field0),
                            b"message" => _serde::__private228::Ok(__Field::__field1),
                            _ => { _serde::__private228::Ok(__Field::__ignore) }
                        }
                    }
                }
                #[automatically_derived]
                impl<'de> _serde::Deserialize<'de> for __Field {
                    #[inline]
                    fn deserialize<__D>(__deserializer: __D)
                        -> _serde::__private228::Result<Self, __D::Error> where
                        __D: _serde::Deserializer<'de> {
                        _serde::Deserializer::deserialize_identifier(__deserializer,
                            __FieldVisitor)
                    }
                }
                #[doc(hidden)]
                struct __Visitor<'de> {
                    marker: _serde::__private228::PhantomData<CodegenError>,
                    lifetime: _serde::__private228::PhantomData<&'de ()>,
                }
                #[automatically_derived]
                impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
                    type Value = CodegenError;
                    fn expecting(&self,
                        __formatter: &mut _serde::__private228::Formatter)
                        -> _serde::__private228::fmt::Result {
                        _serde::__private228::Formatter::write_str(__formatter,
                            "struct CodegenError")
                    }
                    #[inline]
                    fn visit_seq<__A>(self, mut __seq: __A)
                        -> _serde::__private228::Result<Self::Value, __A::Error>
                        where __A: _serde::de::SeqAccess<'de> {
                        let __field0 =
                            match _serde::de::SeqAccess::next_element::<Option<crate::Span>>(&mut __seq)?
                                {
                                _serde::__private228::Some(__value) => __value,
                                _serde::__private228::None =>
                                    return _serde::__private228::Err(_serde::de::Error::invalid_length(0usize,
                                                &"struct CodegenError with 2 elements")),
                            };
                        let __field1 =
                            match _serde::de::SeqAccess::next_element::<String>(&mut __seq)?
                                {
                                _serde::__private228::Some(__value) => __value,
                                _serde::__private228::None =>
                                    return _serde::__private228::Err(_serde::de::Error::invalid_length(1usize,
                                                &"struct CodegenError with 2 elements")),
                            };
                        _serde::__private228::Ok(CodegenError {
                                span: __field0,
                                message: __field1,
                            })
                    }
                    #[inline]
                    fn visit_map<__A>(self, mut __map: __A)
                        -> _serde::__private228::Result<Self::Value, __A::Error>
                        where __A: _serde::de::MapAccess<'de> {
                        let mut __field0:
                                _serde::__private228::Option<Option<crate::Span>> =
                            _serde::__private228::None;
                        let mut __field1: _serde::__private228::Option<String> =
                            _serde::__private228::None;
                        while let _serde::__private228::Some(__key) =
                                _serde::de::MapAccess::next_key::<__Field>(&mut __map)? {
                            match __key {
                                __Field::__field0 => {
                                    if _serde::__private228::Option::is_some(&__field0) {
                                        return _serde::__private228::Err(<__A::Error as
                                                        _serde::de::Error>::duplicate_field("span"));
                                    }
                                    __field0 =
                                        _serde::__private228::Some(_serde::de::MapAccess::next_value::<Option<crate::Span>>(&mut __map)?);
                                }
                                __Field::__field1 => {
                                    if _serde::__private228::Option::is_some(&__field1) {
                                        return _serde::__private228::Err(<__A::Error as
                                                        _serde::de::Error>::duplicate_field("message"));
                                    }
                                    __field1 =
                                        _serde::__private228::Some(_serde::de::MapAccess::next_value::<String>(&mut __map)?);
                                }
                                _ => {
                                    let _ =
                                        _serde::de::MapAccess::next_value::<_serde::de::IgnoredAny>(&mut __map)?;
                                }
                            }
                        }
                        let __field0 =
                            match __field0 {
                                _serde::__private228::Some(__field0) => __field0,
                                _serde::__private228::None =>
                                    _serde::__private228::de::missing_field("span")?,
                            };
                        let __field1 =
                            match __field1 {
                                _serde::__private228::Some(__field1) => __field1,
                                _serde::__private228::None =>
                                    _serde::__private228::de::missing_field("message")?,
                            };
                        _serde::__private228::Ok(CodegenError {
                                span: __field0,
                                message: __field1,
                            })
                    }
                }
                #[doc(hidden)]
                const FIELDS: &'static [&'static str] = &["span", "message"];
                _serde::Deserializer::deserialize_struct(__deserializer,
                    "CodegenError", FIELDS,
                    __Visitor {
                        marker: _serde::__private228::PhantomData::<CodegenError>,
                        lifetime: _serde::__private228::PhantomData,
                    })
            }
        }
    };Deserialize)]
67pub struct CodegenError {
68    /// Optional source span associated with the failure.
69    pub span:    Option<crate::Span>,
70    /// Human-readable error text.
71    pub message: String,
72}
73
74impl CodegenError {
75    fn new(span: Option<crate::Span>, message: impl Into<String>) -> Self {
76        Self {
77            span,
78            message: message.into(),
79        }
80    }
81}
82
83impl fmt::Display for CodegenError {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.write_str(&self.message)
86    }
87}
88
89impl Error for CodegenError {}
90
91impl From<crate::NdbError> for CodegenError {
92    fn from(value: crate::NdbError) -> Self {
93        Self::new(None, value.to_string())
94    }
95}
96
97fn usize_to_i32(value: usize, what: &str) -> Result<i32, CodegenError> {
98    i32::try_from(value)
99        .map_err(|_error| CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} exceeds i32 range", what))
    })format!("{what} exceeds i32 range")))
100}
101
102fn usize_to_u32(value: usize, what: &str) -> Result<u32, CodegenError> {
103    u32::try_from(value)
104        .map_err(|_error| CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} exceeds u32 range", what))
    })format!("{what} exceeds u32 range")))
105}
106
107fn usize_to_u16(value: usize, what: &str) -> Result<u16, CodegenError> {
108    u16::try_from(value)
109        .map_err(|_error| CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} exceeds u16 range", what))
    })format!("{what} exceeds u16 range")))
110}
111
112fn usize_to_u8(value: usize, what: &str) -> Result<u8, CodegenError> {
113    u8::try_from(value)
114        .map_err(|_error| CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} exceeds u8 range", what))
    })format!("{what} exceeds u8 range")))
115}
116
117/// One compilation failure across analysis, lowering, or code generation.
118#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CompileError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            CompileError::Semantic(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Semantic", &__self_0),
            CompileError::Hir(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Hir",
                    &__self_0),
            CompileError::Codegen(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Codegen", &__self_0),
        }
    }
}Debug)]
119pub enum CompileError {
120    /// Semantic analysis failed.
121    Semantic(crate::SemanticError),
122    /// HIR lowering failed.
123    Hir(crate::HirLowerError),
124    /// Code generation failed.
125    Codegen(CodegenError),
126}
127
128impl fmt::Display for CompileError {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            Self::Semantic(error) => error.fmt(f),
132            Self::Hir(error) => error.fmt(f),
133            Self::Codegen(error) => error.fmt(f),
134        }
135    }
136}
137
138impl Error for CompileError {}
139
140impl From<crate::SemanticError> for CompileError {
141    fn from(value: crate::SemanticError) -> Self {
142        Self::Semantic(value)
143    }
144}
145
146impl From<crate::HirLowerError> for CompileError {
147    fn from(value: crate::HirLowerError) -> Self {
148        Self::Hir(value)
149    }
150}
151
152impl From<CodegenError> for CompileError {
153    fn from(value: CodegenError) -> Self {
154        Self::Codegen(value)
155    }
156}
157
158/// Compiles one parsed script through semantic analysis, HIR lowering, and `O0`
159/// NCS emission.
160///
161/// # Errors
162///
163/// Returns [`CompileError`] if semantic analysis, HIR lowering, or NCS emission
164/// fails.
165///
166/// # Examples
167///
168/// ```
169/// let script = nwnrs_nwscript::parse_text(
170///     nwnrs_nwscript::SourceId::new(0),
171///     "void main() {}",
172///     None,
173/// )?;
174/// let artifacts = nwnrs_nwscript::compile_script(
175///     &script,
176///     None,
177///     nwnrs_nwscript::CompileOptions::default(),
178/// )?;
179/// assert!(!artifacts.ncs.is_empty());
180/// assert!(artifacts.ndb.is_none());
181/// # Ok::<(), Box<dyn std::error::Error>>(())
182/// ```
183pub fn compile_script(
184    script: &Script,
185    langspec: Option<&LangSpec>,
186    options: CompileOptions,
187) -> Result<CompileArtifacts, CompileError> {
188    compile_script_with_debug(script, None, None, langspec, options)
189}
190
191/// Compiles one parsed script and emits `NDB` when a source map is available.
192///
193/// # Errors
194///
195/// Returns [`CompileError`] if compilation fails.
196pub fn compile_script_with_source_map(
197    script: &Script,
198    source_map: &SourceMap,
199    root_id: SourceId,
200    langspec: Option<&LangSpec>,
201    options: CompileOptions,
202) -> Result<CompileArtifacts, CompileError> {
203    compile_script_with_debug(script, Some(source_map), Some(root_id), langspec, options)
204}
205
206/// Parses and compiles one already-loaded source bundle with `NDB` output.
207///
208/// # Errors
209///
210/// Returns [`CompileError`] if parsing or compilation fails.
211///
212/// # Examples
213///
214/// ```
215/// let mut resolver = nwnrs_nwscript::InMemoryScriptResolver::new();
216/// resolver.insert_source("main", "void main() {}");
217/// let bundle = nwnrs_nwscript::load_source_bundle(
218///     &resolver,
219///     "main",
220///     nwnrs_nwscript::SourceLoadOptions::default(),
221/// )?;
222/// let artifacts = nwnrs_nwscript::compile_source_bundle(
223///     &bundle,
224///     None,
225///     nwnrs_nwscript::CompileOptions::default(),
226/// )?;
227/// assert!(!artifacts.ncs.is_empty());
228/// assert!(artifacts.ndb.is_some());
229/// # Ok::<(), Box<dyn std::error::Error>>(())
230/// ```
231pub fn compile_source_bundle(
232    bundle: &SourceBundle,
233    langspec: Option<&LangSpec>,
234    options: CompileOptions,
235) -> Result<CompileArtifacts, CompileError> {
236    let script = parse_source_bundle(bundle, langspec).map_err(|error| {
237        CompileError::Codegen(CodegenError::new(
238            None,
239            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("failed to parse source bundle during compile: {0}",
                error))
    })format!("failed to parse source bundle during compile: {error}"),
240        ))
241    })?;
242    compile_script_with_source_map(
243        &script,
244        &bundle.source_map,
245        bundle.root_id,
246        langspec,
247        options,
248    )
249}
250
251fn compile_script_with_debug(
252    script: &Script,
253    source_map: Option<&SourceMap>,
254    root_id: Option<SourceId>,
255    langspec: Option<&LangSpec>,
256    options: CompileOptions,
257) -> Result<CompileArtifacts, CompileError> {
258    let semantic = analyze_script_with_options(script, langspec, options.semantic)?;
259    let hir = lower_to_hir(script, &semantic, langspec)?;
260    if optimization_not_o0(options.optimization) {
261        let ncs = compile_hir_to_ncs(&hir, langspec, options.optimization)?;
262        return Ok(CompileArtifacts {
263            ncs,
264            ndb: None,
265        });
266    }
267
268    let output = O0Compiler::new(&hir, langspec, source_map)?.compile()?;
269    let ncs = encode_ncs_instructions(&output.instructions);
270    let ndb = match (source_map, root_id) {
271        (Some(source_map), Some(root_id)) => {
272            let ndb = build_ndb(&hir, langspec, source_map, root_id, &output)?;
273            let mut bytes = Vec::new();
274            write_ndb(&mut bytes, &ndb).map_err(CodegenError::from)?;
275            Some(bytes)
276        }
277        _ => None,
278    };
279    Ok(CompileArtifacts {
280        ncs,
281        ndb,
282    })
283}
284
285/// Compiles one lowered HIR module to `NCS`.
286///
287/// # Errors
288///
289/// Returns [`CodegenError`] if code generation fails.
290///
291/// # Examples
292///
293/// ```
294/// let script = nwnrs_nwscript::parse_text(
295///     nwnrs_nwscript::SourceId::new(0),
296///     "void main() {}",
297///     None,
298/// )?;
299/// let semantic = nwnrs_nwscript::analyze_script_with_options(
300///     &script,
301///     None,
302///     nwnrs_nwscript::SemanticOptions::default(),
303/// )?;
304/// let hir = nwnrs_nwscript::lower_to_hir(&script, &semantic, None)?;
305/// let ncs = nwnrs_nwscript::compile_hir_to_ncs(
306///     &hir,
307///     None,
308///     nwnrs_nwscript::OptimizationLevel::O0,
309/// )?;
310/// assert!(!ncs.is_empty());
311/// # Ok::<(), Box<dyn std::error::Error>>(())
312/// ```
313pub fn compile_hir_to_ncs(
314    hir: &HirModule,
315    langspec: Option<&LangSpec>,
316    optimization: OptimizationLevel,
317) -> Result<Vec<u8>, CodegenError> {
318    let optimized_hir = if optimization_needs_hir_passes(optimization) {
319        optimize_hir(hir, langspec, optimization)
320    } else {
321        hir.clone()
322    };
323
324    let mut instructions = O0Compiler::new(&optimized_hir, langspec, None)?
325        .compile()?
326        .instructions;
327    if optimization_needs_post_codegen_passes(optimization) {
328        instructions = meld_instructions(instructions);
329    }
330    Ok(encode_ncs_instructions(&instructions))
331}
332
333fn optimization_not_o0(optimization: OptimizationLevel) -> bool {
334    optimization != OptimizationLevel::O0
335}
336
337#[derive(#[automatically_derived]
impl ::core::fmt::Debug for LabelId {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "LabelId",
            &&self.0)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for LabelId {
    #[inline]
    fn clone(&self) -> LabelId {
        let _: ::core::clone::AssertParamIsClone<u32>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for LabelId { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for LabelId {
    #[inline]
    fn eq(&self, other: &LabelId) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for LabelId {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u32>;
    }
}Eq, #[automatically_derived]
impl ::core::cmp::PartialOrd for LabelId {
    #[inline]
    fn partial_cmp(&self, other: &LabelId)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0)
    }
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for LabelId {
    #[inline]
    fn cmp(&self, other: &LabelId) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}Ord, #[automatically_derived]
impl ::core::hash::Hash for LabelId {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.0, state)
    }
}Hash)]
338struct LabelId(u32);
339
340struct ResolvedAssembly {
341    instructions: Vec<NcsInstruction>,
342    offsets:      BTreeMap<LabelId, u32>,
343}
344
345struct FunctionDebugInfo {
346    start: LabelId,
347    end:   LabelId,
348}
349
350struct VariableDebugInfo {
351    name:      String,
352    ty:        SemanticType,
353    start:     LabelId,
354    end:       Option<LabelId>,
355    stack_loc: u32,
356}
357
358struct LineDebugInfo {
359    source_id: SourceId,
360    line_num:  usize,
361    start:     LabelId,
362    end:       LabelId,
363}
364
365struct OpenLineDebug {
366    source_id: SourceId,
367    line_num:  usize,
368    refs:      usize,
369    start:     LabelId,
370}
371
372#[derive(#[automatically_derived]
impl ::core::default::Default for LineDebugTracker {
    #[inline]
    fn default() -> LineDebugTracker {
        LineDebugTracker {
            current: ::core::default::Default::default(),
            entries: ::core::default::Default::default(),
        }
    }
}Default)]
373struct LineDebugTracker {
374    current: Option<OpenLineDebug>,
375    entries: Vec<LineDebugInfo>,
376}
377
378struct CodegenOutput {
379    instructions:  Vec<NcsInstruction>,
380    label_offsets: BTreeMap<LabelId, u32>,
381    functions:     BTreeMap<String, FunctionDebugInfo>,
382    variables:     Vec<VariableDebugInfo>,
383    lines:         Vec<LineDebugInfo>,
384}
385
386enum AssemblyItem {
387    Label(LabelId),
388    Instruction(NcsInstruction),
389    RelativeJump { opcode: NcsOpcode, target: LabelId },
390}
391
392#[derive(#[automatically_derived]
impl ::core::default::Default for Assembler {
    #[inline]
    fn default() -> Assembler {
        Assembler {
            items: ::core::default::Default::default(),
            next_label: ::core::default::Default::default(),
        }
    }
}Default)]
393struct Assembler {
394    items:      Vec<AssemblyItem>,
395    next_label: u32,
396}
397
398impl Assembler {
399    fn new_label(&mut self) -> LabelId {
400        let label = LabelId(self.next_label);
401        self.next_label += 1;
402        label
403    }
404
405    fn place_label(&mut self, label: LabelId) {
406        self.items.push(AssemblyItem::Label(label));
407    }
408
409    fn push(&mut self, instruction: NcsInstruction) {
410        self.items.push(AssemblyItem::Instruction(instruction));
411    }
412
413    fn push_jump(&mut self, opcode: NcsOpcode, target: LabelId) {
414        self.items.push(AssemblyItem::RelativeJump {
415            opcode,
416            target,
417        });
418    }
419
420    fn finalize(self) -> Result<ResolvedAssembly, CodegenError> {
421        let mut offsets = BTreeMap::new();
422        let mut offset = 0usize;
423        for item in &self.items {
424            match item {
425                AssemblyItem::Label(label) => {
426                    offsets.insert(*label, offset);
427                }
428                AssemblyItem::Instruction(instruction) => {
429                    offset += instruction.encoded_len();
430                }
431                AssemblyItem::RelativeJump {
432                    ..
433                } => {
434                    offset += NCS_OPERATION_BASE_SIZE + 4;
435                }
436            }
437        }
438
439        let mut instructions = Vec::new();
440        let mut offset = 0usize;
441        for item in self.items {
442            match item {
443                AssemblyItem::Label(_) => {}
444                AssemblyItem::Instruction(instruction) => {
445                    offset += instruction.encoded_len();
446                    instructions.push(instruction);
447                }
448                AssemblyItem::RelativeJump {
449                    opcode,
450                    target,
451                } => {
452                    let target_offset = offsets.get(&target).copied().ok_or_else(|| {
453                        CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unresolved code label {0:?}",
                target))
    })format!("unresolved code label {target:?}"))
454                    })?;
455                    let delta = usize_to_i32(target_offset, "jump target offset")?
456                        - usize_to_i32(offset, "jump offset")?;
457                    let instruction = NcsInstruction {
458                        opcode,
459                        auxcode: NcsAuxCode::None,
460                        extra: delta.to_be_bytes().to_vec(),
461                    };
462                    offset += instruction.encoded_len();
463                    instructions.push(instruction);
464                }
465            }
466        }
467
468        Ok(ResolvedAssembly {
469            instructions,
470            offsets: offsets
471                .into_iter()
472                .map(|(label, offset)| {
473                    Ok::<_, CodegenError>((label, usize_to_u32(offset, "label offset")?))
474                })
475                .collect::<Result<_, _>>()?,
476        })
477    }
478}
479
480struct O0Compiler<'a> {
481    hir:                   &'a HirModule,
482    langspec:              Option<&'a LangSpec>,
483    builtin_functions:     BTreeMap<String, (u16, &'a BuiltinFunction)>,
484    builtin_constants:     BTreeMap<String, BuiltinValue>,
485    constant_env:          BTreeMap<String, ConstValue>,
486    structs:               BTreeMap<String, &'a crate::HirStruct>,
487    functions:             BTreeMap<String, &'a HirFunction>,
488    entry_function:        Option<&'a HirFunction>,
489    global_layout:         BTreeMap<String, ValueLayout>,
490    global_size:           usize,
491    function_labels:       BTreeMap<String, LabelId>,
492    function_exit_labels:  BTreeMap<String, LabelId>,
493    function_end_labels:   BTreeMap<String, LabelId>,
494    globals_label:         Option<LabelId>,
495    globals_end_label:     Option<LabelId>,
496    variable_debug:        Vec<VariableDebugInfo>,
497    line_debug:            LineDebugTracker,
498    source_map:            Option<&'a SourceMap>,
499    current_function_name: Option<&'a str>,
500    compile_time:          SystemTime,
501    assembler:             Assembler,
502}
503
504#[derive(#[automatically_derived]
impl ::core::clone::Clone for ValueLayout {
    #[inline]
    fn clone(&self) -> ValueLayout {
        ValueLayout {
            offset: ::core::clone::Clone::clone(&self.offset),
            size: ::core::clone::Clone::clone(&self.size),
        }
    }
}Clone)]
505struct ValueLayout {
506    offset: usize,
507    size:   usize,
508}
509
510#[derive(#[automatically_derived]
impl ::core::clone::Clone for FieldLayout {
    #[inline]
    fn clone(&self) -> FieldLayout {
        FieldLayout {
            ty: ::core::clone::Clone::clone(&self.ty),
            offset: ::core::clone::Clone::clone(&self.offset),
            size: ::core::clone::Clone::clone(&self.size),
        }
    }
}Clone)]
511struct FieldLayout {
512    ty:     SemanticType,
513    offset: usize,
514    size:   usize,
515}
516
517#[derive(#[automatically_derived]
impl ::core::clone::Clone for FunctionLayout {
    #[inline]
    fn clone(&self) -> FunctionLayout {
        FunctionLayout {
            return_layout: ::core::clone::Clone::clone(&self.return_layout),
            locals: ::core::clone::Clone::clone(&self.locals),
            locals_size: ::core::clone::Clone::clone(&self.locals_size),
        }
    }
}Clone)]
518struct FunctionLayout {
519    return_layout: Option<ValueLayout>,
520    locals:        BTreeMap<HirLocalId, ValueLayout>,
521    locals_size:   usize,
522}
523
524struct FunctionEmitter<'a, 'b> {
525    compiler:         &'b mut O0Compiler<'a>,
526    function:         &'a HirFunction,
527    layout:           FunctionLayout,
528    temp_bytes:       usize,
529    break_targets:    Vec<LabelId>,
530    continue_targets: Vec<LabelId>,
531    scope_stack:      Vec<Vec<usize>>,
532}
533
534impl<'a> O0Compiler<'a> {
535    fn new(
536        hir: &'a HirModule,
537        langspec: Option<&'a LangSpec>,
538        source_map: Option<&'a SourceMap>,
539    ) -> Result<Self, CodegenError> {
540        let mut builtin_functions = BTreeMap::new();
541        let mut builtin_constants = BTreeMap::new();
542        if let Some(langspec) = langspec {
543            for (index, function) in langspec.functions.iter().enumerate() {
544                builtin_functions.insert(
545                    function.name.clone(),
546                    (usize_to_u16(index, "builtin function index")?, function),
547                );
548            }
549            for constant in &langspec.constants {
550                builtin_constants.insert(constant.name.clone(), constant.value.clone());
551            }
552        }
553        let constant_env = build_constant_env(hir, langspec);
554
555        let structs = hir
556            .structs
557            .iter()
558            .map(|structure| (structure.name.clone(), structure))
559            .collect::<BTreeMap<_, _>>();
560        let functions = hir
561            .functions
562            .iter()
563            .map(|function| (function.name.clone(), function))
564            .collect::<BTreeMap<_, _>>();
565        let entry_function = functions
566            .get("main")
567            .copied()
568            .or_else(|| functions.get("StartingConditional").copied());
569
570        let mut global_layout = BTreeMap::new();
571        let mut global_size = 0usize;
572        for global in &hir.globals {
573            let size = size_of_type(&global.ty, &structs)?;
574            global_layout.insert(
575                global.name.clone(),
576                ValueLayout {
577                    offset: global_size,
578                    size,
579                },
580            );
581            global_size += size;
582        }
583
584        let mut assembler = Assembler::default();
585        let globals_label = (!hir.globals.is_empty()).then(|| assembler.new_label());
586        let globals_end_label = globals_label.map(|_| assembler.new_label());
587        let function_labels = hir
588            .functions
589            .iter()
590            .map(|function| (function.name.clone(), assembler.new_label()))
591            .collect::<BTreeMap<_, _>>();
592        let function_end_labels = hir
593            .functions
594            .iter()
595            .map(|function| (function.name.clone(), assembler.new_label()))
596            .collect::<BTreeMap<_, _>>();
597        let function_exit_labels = hir
598            .functions
599            .iter()
600            .map(|function| (function.name.clone(), assembler.new_label()))
601            .collect::<BTreeMap<_, _>>();
602
603        Ok(Self {
604            hir,
605            langspec,
606            builtin_functions,
607            builtin_constants,
608            constant_env,
609            structs,
610            functions,
611            entry_function,
612            global_layout,
613            global_size,
614            function_labels,
615            function_exit_labels,
616            function_end_labels,
617            globals_label,
618            globals_end_label,
619            variable_debug: Vec::new(),
620            line_debug: LineDebugTracker::default(),
621            source_map,
622            current_function_name: None,
623            compile_time: SystemTime::now(),
624            assembler,
625        })
626    }
627
628    fn compile(mut self) -> Result<CodegenOutput, CodegenError> {
629        self.emit_loader()?;
630
631        if let Some(globals_label) = self.globals_label {
632            self.assembler.place_label(globals_label);
633            self.emit_globals()?;
634            if let Some(end) = self.globals_end_label {
635                self.assembler.place_label(end);
636            }
637        }
638
639        for function in &self.hir.functions {
640            if function.is_builtin {
641                continue;
642            }
643            let label = self
644                .function_labels
645                .get(&function.name)
646                .copied()
647                .ok_or_else(|| {
648                    CodegenError::new(
649                        Some(function.span),
650                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function label for {0:?}",
                function.name))
    })format!("missing function label for {:?}", function.name),
651                    )
652                })?;
653            self.assembler.place_label(label);
654            self.emit_function(function)?;
655            let end_label = self
656                .function_end_labels
657                .get(&function.name)
658                .copied()
659                .ok_or_else(|| {
660                    CodegenError::new(
661                        Some(function.span),
662                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function end label for {0:?}",
                function.name))
    })format!("missing function end label for {:?}", function.name),
663                    )
664                })?;
665            self.assembler.place_label(end_label);
666        }
667
668        let assembly = self.assembler.finalize()?;
669        Ok(CodegenOutput {
670            instructions:  assembly.instructions,
671            label_offsets: assembly.offsets,
672            functions:     self
673                .function_labels
674                .iter()
675                .map(|(name, start)| {
676                    let end = self.function_end_labels.get(name).copied().ok_or_else(|| {
677                        CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function end label for {0:?}",
                name))
    })format!("missing function end label for {name:?}"))
678                    })?;
679                    Ok::<_, CodegenError>((
680                        name.clone(),
681                        FunctionDebugInfo {
682                            start: *start,
683                            end,
684                        },
685                    ))
686                })
687                .collect::<Result<_, _>>()?,
688            variables:     self.variable_debug,
689            lines:         self.line_debug.entries,
690        })
691    }
692
693    fn emit_loader(&mut self) -> Result<(), CodegenError> {
694        if let Some(entry) = self.entry_function
695            && entry.return_type != SemanticType::Void
696        {
697            self.emit_stack_alloc(&entry.return_type)?;
698            let start = self.assembler.new_label();
699            self.assembler.place_label(start);
700            self.variable_debug.push(VariableDebugInfo {
701                name: "#retval".to_string(),
702                ty: entry.return_type.clone(),
703                start,
704                end: None,
705                stack_loc: 0,
706            });
707        }
708
709        if let Some(globals_label) = self.globals_label {
710            self.assembler.push_jump(NcsOpcode::Jsr, globals_label);
711        } else if let Some(entry) = self.entry_function {
712            let label = self
713                .function_labels
714                .get(&entry.name)
715                .copied()
716                .ok_or_else(|| {
717                    CodegenError::new(
718                        Some(entry.span),
719                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function label for {0:?}",
                entry.name))
    })format!("missing function label for {:?}", entry.name),
720                    )
721                })?;
722            self.assembler.push_jump(NcsOpcode::Jsr, label);
723        }
724
725        self.assembler.push(simple_instruction(NcsOpcode::Ret));
726        Ok(())
727    }
728
729    fn emit_globals(&mut self) -> Result<(), CodegenError> {
730        if self.globals_label.is_some() {
731            for global in &self.hir.globals {
732                self.emit_stack_alloc(&global.ty)?;
733                let start = self.assembler.new_label();
734                self.assembler.place_label(start);
735                let layout = self.global_layout.get(&global.name).ok_or_else(|| {
736                    CodegenError::new(
737                        Some(global.span),
738                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown global {0:?}",
                global.name))
    })format!("unknown global {:?}", global.name),
739                    )
740                })?;
741                self.variable_debug.push(VariableDebugInfo {
742                    name: global.name.clone(),
743                    ty: global.ty.clone(),
744                    start,
745                    end: None,
746                    stack_loc: usize_to_u32(layout.offset, "global stack location")?,
747                });
748            }
749        }
750        self.assembler
751            .push(simple_instruction(NcsOpcode::SaveBasePointer));
752
753        let mut emitter = GlobalEmitter {
754            compiler:   self,
755            temp_bytes: 0,
756        };
757        for global in &emitter.compiler.hir.globals {
758            if let Some(initializer) = &global.initializer {
759                let start = emitter.compiler.assembler.new_label();
760                emitter.compiler.assembler.place_label(start);
761                emitter.compiler.start_line_at(global.span, start);
762                emitter.emit_expr(initializer)?;
763                emitter.emit_store_global(&global.name, initializer.span)?;
764                emitter.emit_pop_type(&initializer.ty)?;
765                let end = emitter.compiler.assembler.new_label();
766                emitter.compiler.assembler.place_label(end);
767                emitter.compiler.end_line_at(global.span, end);
768            }
769        }
770
771        if let Some(entry) = emitter.compiler.entry_function {
772            if entry.return_type != SemanticType::Void {
773                emitter.compiler.emit_stack_alloc(&entry.return_type)?;
774            }
775            let label = emitter
776                .compiler
777                .function_labels
778                .get(&entry.name)
779                .copied()
780                .ok_or_else(|| {
781                    CodegenError::new(
782                        Some(entry.span),
783                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function label for {0:?}",
                entry.name))
    })format!("missing function label for {:?}", entry.name),
784                    )
785                })?;
786            emitter.compiler.assembler.push_jump(NcsOpcode::Jsr, label);
787        }
788
789        emitter
790            .compiler
791            .assembler
792            .push(simple_instruction(NcsOpcode::RestoreBasePointer));
793        emitter
794            .compiler
795            .assembler
796            .push(simple_instruction(NcsOpcode::Ret));
797        Ok(())
798    }
799
800    fn emit_function(&mut self, function: &'a HirFunction) -> Result<(), CodegenError> {
801        let previous_function_name = self.current_function_name.replace(function.name.as_str());
802        let result = (|| {
803            let layout = self.function_layout(function)?;
804            let start = self
805                .function_labels
806                .get(&function.name)
807                .copied()
808                .ok_or_else(|| {
809                    CodegenError::new(
810                        Some(function.span),
811                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function label for {0:?}",
                function.name))
    })format!("missing function label for {:?}", function.name),
812                    )
813                })?;
814            let exit = self
815                .function_exit_labels
816                .get(&function.name)
817                .copied()
818                .ok_or_else(|| {
819                    CodegenError::new(
820                        Some(function.span),
821                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function exit label for {0:?}",
                function.name))
    })format!("missing function exit label for {:?}", function.name),
822                    )
823                })?;
824            let end = self
825                .function_end_labels
826                .get(&function.name)
827                .copied()
828                .ok_or_else(|| {
829                    CodegenError::new(
830                        Some(function.span),
831                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function end label for {0:?}",
                function.name))
    })format!("missing function end label for {:?}", function.name),
832                    )
833                })?;
834            if let Some(retval) = &layout.return_layout {
835                self.variable_debug.push(VariableDebugInfo {
836                    name: "#retval".to_string(),
837                    ty: function.return_type.clone(),
838                    start,
839                    end: Some(end),
840                    stack_loc: usize_to_u32(retval.offset, "return stack location")?,
841                });
842            }
843            for parameter in &function.parameters {
844                let slot = layout.locals.get(&parameter.local).ok_or_else(|| {
845                    CodegenError::new(
846                        Some(function.span),
847                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown local slot {0:?}",
                parameter.local))
    })format!("unknown local slot {:?}", parameter.local),
848                    )
849                })?;
850                self.variable_debug.push(VariableDebugInfo {
851                    name: parameter.name.clone(),
852                    ty: parameter.ty.clone(),
853                    start,
854                    end: Some(end),
855                    stack_loc: usize_to_u32(slot.offset, "parameter stack location")?,
856                });
857            }
858            let mut emitter = FunctionEmitter {
859                compiler: self,
860                function,
861                layout,
862                temp_bytes: 0,
863                break_targets: Vec::new(),
864                continue_targets: Vec::new(),
865                scope_stack: Vec::new(),
866            };
867            emitter.emit_prologue()?;
868            if let Some(body) = &function.body {
869                emitter.emit_block(body)?;
870                emitter.compiler.assembler.place_label(exit);
871                let final_line_start = emitter.compiler.assembler.new_label();
872                emitter.compiler.assembler.place_label(final_line_start);
873                emitter
874                    .compiler
875                    .start_line_end_at(body.span, final_line_start);
876                if function.return_type == SemanticType::Void {
877                    emitter.emit_function_epilogue();
878                }
879                emitter
880                    .compiler
881                    .assembler
882                    .push(simple_instruction(NcsOpcode::Ret));
883                let final_line_end = emitter.compiler.assembler.new_label();
884                emitter.compiler.assembler.place_label(final_line_end);
885                emitter.compiler.end_line_end_at(body.span, final_line_end);
886            } else if function.return_type == SemanticType::Void {
887                emitter.compiler.assembler.place_label(exit);
888                emitter.emit_function_epilogue();
889                emitter
890                    .compiler
891                    .assembler
892                    .push(simple_instruction(NcsOpcode::Ret));
893            } else {
894                emitter.compiler.assembler.place_label(exit);
895                emitter
896                    .compiler
897                    .assembler
898                    .push(simple_instruction(NcsOpcode::Ret));
899            }
900            Ok(())
901        })();
902        self.current_function_name = previous_function_name;
903        result
904    }
905
906    fn function_layout(&self, function: &HirFunction) -> Result<FunctionLayout, CodegenError> {
907        let mut offset = 0usize;
908        let return_layout = if function.return_type == SemanticType::Void {
909            None
910        } else {
911            let size = size_of_type(&function.return_type, &self.structs)?;
912            let layout = ValueLayout {
913                offset,
914                size,
915            };
916            offset += size;
917            Some(layout)
918        };
919
920        let mut locals = BTreeMap::new();
921        for parameter in &function.parameters {
922            let size = size_of_type(&parameter.ty, &self.structs)?;
923            locals.insert(
924                parameter.local,
925                ValueLayout {
926                    offset,
927                    size,
928                },
929            );
930            offset += size;
931        }
932
933        let frame_prefix = offset;
934        for local in &function.locals {
935            if local.kind != HirLocalKind::Local {
936                continue;
937            }
938            let size = size_of_type(&local.ty, &self.structs)?;
939            locals.insert(
940                local.id,
941                ValueLayout {
942                    offset,
943                    size,
944                },
945            );
946            offset += size;
947        }
948
949        Ok(FunctionLayout {
950            return_layout,
951            locals,
952            locals_size: offset - frame_prefix,
953        })
954    }
955
956    fn emit_stack_alloc(&mut self, ty: &SemanticType) -> Result<(), CodegenError> {
957        match ty {
958            SemanticType::Int => self.assembler.push(simple_aux_instruction(
959                NcsOpcode::RunstackAdd,
960                NcsAuxCode::TypeInteger,
961            )),
962            SemanticType::Float => self.assembler.push(simple_aux_instruction(
963                NcsOpcode::RunstackAdd,
964                NcsAuxCode::TypeFloat,
965            )),
966            SemanticType::String => self.assembler.push(simple_aux_instruction(
967                NcsOpcode::RunstackAdd,
968                NcsAuxCode::TypeString,
969            )),
970            SemanticType::Object => self.assembler.push(simple_aux_instruction(
971                NcsOpcode::RunstackAdd,
972                NcsAuxCode::TypeObject,
973            )),
974            SemanticType::EngineStructure(name) => self.assembler.push(simple_aux_instruction(
975                NcsOpcode::RunstackAdd,
976                aux_for_engine_structure(name, self.hir, &self.structs)?,
977            )),
978            SemanticType::Vector => {
979                self.emit_stack_alloc(&SemanticType::Float)?;
980                self.emit_stack_alloc(&SemanticType::Float)?;
981                self.emit_stack_alloc(&SemanticType::Float)?;
982            }
983            SemanticType::Struct(name) => {
984                let structure = self.structs.get(name).ok_or_else(|| {
985                    CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown structure {0:?}", name))
    })format!("unknown structure {name:?}"))
986                })?;
987                for field in &structure.fields {
988                    self.emit_stack_alloc(&field.ty)?;
989                }
990            }
991            SemanticType::Void | SemanticType::Action => {}
992        }
993        Ok(())
994    }
995
996    fn start_line_at(&mut self, span: crate::Span, label: LabelId) {
997        let Some((source_id, line_num)) = self.line_location(span) else {
998            return;
999        };
1000        self.start_line(source_id, line_num, label);
1001    }
1002
1003    fn start_line_end_at(&mut self, span: crate::Span, label: LabelId) {
1004        let Some((source_id, line_num)) = self.line_end_location(span) else {
1005            return;
1006        };
1007        self.start_line(source_id, line_num, label);
1008    }
1009
1010    fn start_line(&mut self, source_id: SourceId, line_num: usize, label: LabelId) {
1011        match &mut self.line_debug.current {
1012            Some(current) if current.source_id == source_id && current.line_num == line_num => {
1013                current.refs += 1;
1014            }
1015            _ => {
1016                self.line_debug.current = Some(OpenLineDebug {
1017                    source_id,
1018                    line_num,
1019                    refs: 1,
1020                    start: label,
1021                });
1022            }
1023        }
1024    }
1025
1026    fn end_line_at(&mut self, span: crate::Span, label: LabelId) {
1027        let Some((source_id, line_num)) = self.line_location(span) else {
1028            self.line_debug.current = None;
1029            return;
1030        };
1031        self.end_line(source_id, line_num, label);
1032    }
1033
1034    fn end_line_end_at(&mut self, span: crate::Span, label: LabelId) {
1035        let Some((source_id, line_num)) = self.line_end_location(span) else {
1036            self.line_debug.current = None;
1037            return;
1038        };
1039        self.end_line(source_id, line_num, label);
1040    }
1041
1042    fn end_line(&mut self, source_id: SourceId, line_num: usize, label: LabelId) {
1043        let Some(current) = &mut self.line_debug.current else {
1044            return;
1045        };
1046        if current.source_id != source_id || current.line_num != line_num {
1047            self.line_debug.current = None;
1048            return;
1049        }
1050        if current.refs > 1 {
1051            current.refs -= 1;
1052            return;
1053        }
1054        let Some(current) = self.line_debug.current.take() else {
1055            return;
1056        };
1057        self.line_debug.entries.push(LineDebugInfo {
1058            source_id: current.source_id,
1059            line_num:  current.line_num,
1060            start:     current.start,
1061            end:       label,
1062        });
1063    }
1064
1065    fn line_location(&self, span: crate::Span) -> Option<(SourceId, usize)> {
1066        let source_map = self.source_map?;
1067        let file = source_map.get(span.source_id)?;
1068        let location = file.location(span.start)?;
1069        Some((span.source_id, location.line))
1070    }
1071
1072    fn line_end_location(&self, span: crate::Span) -> Option<(SourceId, usize)> {
1073        let source_map = self.source_map?;
1074        let file = source_map.get(span.source_id)?;
1075        let position = if span.end > span.start {
1076            span.end - 1
1077        } else {
1078            span.start
1079        };
1080        let location = file.location(position)?;
1081        Some((span.source_id, location.line))
1082    }
1083
1084    fn magic_literal_value(
1085        &self,
1086        literal: crate::MagicLiteral,
1087        span: Option<crate::Span>,
1088    ) -> Literal {
1089        match literal {
1090            crate::MagicLiteral::Function => {
1091                Literal::String(self.current_function_name.unwrap_or_default().to_string())
1092            }
1093            crate::MagicLiteral::File => {
1094                let value = span
1095                    .and_then(|span| self.source_map?.get(span.source_id))
1096                    .map(|file| file.name.clone())
1097                    .unwrap_or_default();
1098                Literal::String(value)
1099            }
1100            crate::MagicLiteral::Line => {
1101                let value = span
1102                    .and_then(|span| self.line_location(span))
1103                    .map_or(0, |(_source_id, line)| {
1104                        i32::try_from(line).ok().unwrap_or(i32::MAX)
1105                    });
1106                Literal::Integer(value)
1107            }
1108            crate::MagicLiteral::Date => Literal::String(format_magic_date(self.compile_time)),
1109            crate::MagicLiteral::Time => Literal::String(format_magic_time(self.compile_time)),
1110        }
1111    }
1112}
1113
1114struct GlobalEmitter<'a, 'b> {
1115    compiler:   &'b mut O0Compiler<'a>,
1116    temp_bytes: usize,
1117}
1118
1119impl GlobalEmitter<'_, '_> {
1120    fn emit_expr(&mut self, expr: &HirExpr) -> Result<(), CodegenError> {
1121        emit_expr_common(self.compiler, &mut self.temp_bytes, None, expr)
1122    }
1123
1124    fn emit_store_global(&mut self, name: &str, span: crate::Span) -> Result<(), CodegenError> {
1125        let layout = self
1126            .compiler
1127            .global_layout
1128            .get(name)
1129            .ok_or_else(|| CodegenError::new(Some(span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown global {0:?}", name))
    })format!("unknown global {name:?}")))?;
1130        let offset = usize_to_i32(layout.offset, "global offset")?
1131            - usize_to_i32(self.compiler.global_size, "global size")?;
1132        self.compiler.assembler.push(NcsInstruction {
1133            opcode:  NcsOpcode::AssignmentBase,
1134            auxcode: NcsAuxCode::TypeVoid,
1135            extra:   assignment_extra(offset, layout.size),
1136        });
1137        Ok(())
1138    }
1139
1140    fn emit_pop_type(&mut self, ty: &SemanticType) -> Result<(), CodegenError> {
1141        let size = size_of_type(ty, &self.compiler.structs)?;
1142        if size > 0 {
1143            self.temp_bytes = self.temp_bytes.saturating_sub(size);
1144            self.compiler.assembler.push(NcsInstruction {
1145                opcode:  NcsOpcode::ModifyStackPointer,
1146                auxcode: NcsAuxCode::None,
1147                extra:   (-usize_to_i32(size, "stack pop size")?)
1148                    .to_be_bytes()
1149                    .to_vec(),
1150            });
1151        }
1152        Ok(())
1153    }
1154}
1155
1156impl FunctionEmitter<'_, '_> {
1157    fn emit_prologue(&mut self) -> Result<(), CodegenError> {
1158        for local in &self.function.locals {
1159            if local.kind == HirLocalKind::Local {
1160                self.compiler.emit_stack_alloc(&local.ty)?;
1161            }
1162        }
1163        Ok(())
1164    }
1165
1166    fn emit_block(&mut self, block: &HirBlock) -> Result<(), CodegenError> {
1167        self.scope_stack.push(Vec::new());
1168        for statement in &block.statements {
1169            self.emit_stmt(statement)?;
1170        }
1171        self.close_scope_variables();
1172        Ok(())
1173    }
1174
1175    #[allow(clippy::too_many_lines)]
1176    fn emit_stmt(&mut self, statement: &HirStmt) -> Result<(), CodegenError> {
1177        match statement {
1178            HirStmt::Block(block) => self.emit_block(block),
1179            HirStmt::Declare(statement) => {
1180                let start = self.compiler.assembler.new_label();
1181                self.compiler.assembler.place_label(start);
1182                self.compiler.start_line_at(statement.span, start);
1183                let start = self.compiler.assembler.new_label();
1184                self.compiler.assembler.place_label(start);
1185                for declarator in &statement.declarators {
1186                    let local = self.local_info(declarator.local, statement.span)?;
1187                    let end_index = self.compiler.variable_debug.len();
1188                    self.compiler.variable_debug.push(VariableDebugInfo {
1189                        name: local.name.clone(),
1190                        ty: local.ty.clone(),
1191                        start,
1192                        end: None,
1193                        stack_loc: self.local_stack_loc(declarator.local, statement.span)?,
1194                    });
1195                    self.current_scope_variables().push(end_index);
1196                }
1197                for declarator in &statement.declarators {
1198                    if let Some(initializer) = &declarator.initializer {
1199                        self.emit_expr(initializer)?;
1200                        self.emit_store_local(declarator.local, initializer.span)?;
1201                        self.emit_pop_type(&initializer.ty)?;
1202                    }
1203                }
1204                let end = self.compiler.assembler.new_label();
1205                self.compiler.assembler.place_label(end);
1206                self.compiler.end_line_at(statement.span, end);
1207                Ok(())
1208            }
1209            HirStmt::Expr(expr) => {
1210                let start = self.compiler.assembler.new_label();
1211                self.compiler.assembler.place_label(start);
1212                self.compiler.start_line_at(expr.span, start);
1213                self.emit_expr(expr)?;
1214                self.emit_pop_type(&expr.ty)?;
1215                let end = self.compiler.assembler.new_label();
1216                self.compiler.assembler.place_label(end);
1217                self.compiler.end_line_at(expr.span, end);
1218                Ok(())
1219            }
1220            HirStmt::If(statement) => {
1221                let stmt_start = self.compiler.assembler.new_label();
1222                self.compiler.assembler.place_label(stmt_start);
1223                self.compiler.start_line_at(statement.span, stmt_start);
1224                let cond_start = self.compiler.assembler.new_label();
1225                self.compiler.assembler.place_label(cond_start);
1226                self.compiler
1227                    .start_line_at(statement.condition.span, cond_start);
1228                let else_label = self.compiler.assembler.new_label();
1229                let end_label = self.compiler.assembler.new_label();
1230                self.emit_expr(&statement.condition)?;
1231                self.emit_branch_zero(else_label)?;
1232                let cond_end = self.compiler.assembler.new_label();
1233                self.compiler.assembler.place_label(cond_end);
1234                self.compiler
1235                    .end_line_at(statement.condition.span, cond_end);
1236                self.emit_stmt(&statement.then_branch)?;
1237                self.compiler.assembler.push_jump(NcsOpcode::Jmp, end_label);
1238                self.compiler.assembler.place_label(else_label);
1239                if let Some(else_branch) = &statement.else_branch {
1240                    let choice_start = self.compiler.assembler.new_label();
1241                    self.compiler.assembler.place_label(choice_start);
1242                    self.compiler.start_line_at(statement.span, choice_start);
1243                    self.compiler
1244                        .assembler
1245                        .push(simple_instruction(NcsOpcode::NoOperation));
1246                    let choice_end = self.compiler.assembler.new_label();
1247                    self.compiler.assembler.place_label(choice_end);
1248                    self.compiler.end_line_at(statement.span, choice_end);
1249                    self.emit_stmt(else_branch)?;
1250                }
1251                self.compiler.assembler.place_label(end_label);
1252                let stmt_end = self.compiler.assembler.new_label();
1253                self.compiler.assembler.place_label(stmt_end);
1254                self.compiler.end_line_at(statement.span, stmt_end);
1255                Ok(())
1256            }
1257            HirStmt::Switch(statement) => {
1258                let stmt_start = self.compiler.assembler.new_label();
1259                self.compiler.assembler.place_label(stmt_start);
1260                self.compiler.start_line_at(statement.span, stmt_start);
1261                let result = self.emit_switch(statement);
1262                let stmt_end = self.compiler.assembler.new_label();
1263                self.compiler.assembler.place_label(stmt_end);
1264                self.compiler.end_line_at(statement.span, stmt_end);
1265                result
1266            }
1267            HirStmt::Return(statement) => {
1268                let start = self.compiler.assembler.new_label();
1269                self.compiler.assembler.place_label(start);
1270                self.compiler.start_line_at(statement.span, start);
1271                if let Some(value) = &statement.value {
1272                    self.emit_expr(value)?;
1273                    let Some(retval) = &self.layout.return_layout else {
1274                        return Err(CodegenError::new(
1275                            Some(statement.span),
1276                            "return value in void function during code generation",
1277                        ));
1278                    };
1279                    let offset = usize_to_i32(retval.offset, "return slot offset")?
1280                        - usize_to_i32(self.current_stack_bytes(), "current stack bytes")?;
1281                    self.compiler.assembler.push(NcsInstruction {
1282                        opcode:  NcsOpcode::Assignment,
1283                        auxcode: NcsAuxCode::TypeVoid,
1284                        extra:   assignment_extra(offset, retval.size),
1285                    });
1286                }
1287                self.emit_function_epilogue();
1288                let exit = self
1289                    .compiler
1290                    .function_exit_labels
1291                    .get(&self.function.name)
1292                    .copied()
1293                    .ok_or_else(|| {
1294                        CodegenError::new(
1295                            Some(statement.span),
1296                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function exit label for {0:?}",
                self.function.name))
    })format!("missing function exit label for {:?}", self.function.name),
1297                        )
1298                    })?;
1299                self.compiler.assembler.push_jump(NcsOpcode::Jmp, exit);
1300                let end = self.compiler.assembler.new_label();
1301                self.compiler.assembler.place_label(end);
1302                self.compiler.end_line_at(statement.span, end);
1303                Ok(())
1304            }
1305            HirStmt::While(statement) => {
1306                let stmt_start = self.compiler.assembler.new_label();
1307                self.compiler.assembler.place_label(stmt_start);
1308                self.compiler.start_line_at(statement.span, stmt_start);
1309                let cond_label = self.compiler.assembler.new_label();
1310                let end_label = self.compiler.assembler.new_label();
1311                self.compiler.assembler.place_label(cond_label);
1312                let loop_start = self.compiler.assembler.new_label();
1313                self.compiler.assembler.place_label(loop_start);
1314                self.compiler.start_line_at(statement.span, loop_start);
1315                self.emit_expr(&statement.condition)?;
1316                self.emit_branch_zero(end_label)?;
1317                let loop_end = self.compiler.assembler.new_label();
1318                self.compiler.assembler.place_label(loop_end);
1319                self.compiler.end_line_at(statement.span, loop_end);
1320                self.break_targets.push(end_label);
1321                self.continue_targets.push(cond_label);
1322                self.emit_stmt(&statement.body)?;
1323                self.continue_targets.pop();
1324                self.break_targets.pop();
1325                self.compiler
1326                    .assembler
1327                    .push_jump(NcsOpcode::Jmp, cond_label);
1328                self.compiler.assembler.place_label(end_label);
1329                let stmt_end = self.compiler.assembler.new_label();
1330                self.compiler.assembler.place_label(stmt_end);
1331                self.compiler.end_line_at(statement.span, stmt_end);
1332                Ok(())
1333            }
1334            HirStmt::DoWhile(statement) => {
1335                let stmt_start = self.compiler.assembler.new_label();
1336                self.compiler.assembler.place_label(stmt_start);
1337                self.compiler.start_line_at(statement.span, stmt_start);
1338                let body_label = self.compiler.assembler.new_label();
1339                let cond_label = self.compiler.assembler.new_label();
1340                let end_label = self.compiler.assembler.new_label();
1341                self.compiler.assembler.place_label(body_label);
1342                self.break_targets.push(end_label);
1343                self.continue_targets.push(cond_label);
1344                self.emit_stmt(&statement.body)?;
1345                self.continue_targets.pop();
1346                self.break_targets.pop();
1347                self.compiler.assembler.place_label(cond_label);
1348                let continue_start = self.compiler.assembler.new_label();
1349                self.compiler.assembler.place_label(continue_start);
1350                self.compiler.start_line_at(statement.span, continue_start);
1351                self.emit_expr(&statement.condition)?;
1352                self.emit_branch_zero(end_label)?;
1353                self.compiler
1354                    .assembler
1355                    .push_jump(NcsOpcode::Jmp, body_label);
1356                self.compiler.assembler.place_label(end_label);
1357                let continue_end = self.compiler.assembler.new_label();
1358                self.compiler.assembler.place_label(continue_end);
1359                self.compiler.end_line_at(statement.span, continue_end);
1360                let stmt_end = self.compiler.assembler.new_label();
1361                self.compiler.assembler.place_label(stmt_end);
1362                self.compiler.end_line_at(statement.span, stmt_end);
1363                Ok(())
1364            }
1365            HirStmt::For(statement) => {
1366                let start = self.compiler.assembler.new_label();
1367                self.compiler.assembler.place_label(start);
1368                self.compiler.start_line_at(statement.span, start);
1369                if let Some(initializer) = &statement.initializer {
1370                    self.emit_expr(initializer)?;
1371                    self.emit_pop_type(&initializer.ty)?;
1372                }
1373                let cond_label = self.compiler.assembler.new_label();
1374                let update_label = self.compiler.assembler.new_label();
1375                let end_label = self.compiler.assembler.new_label();
1376                self.compiler.assembler.place_label(cond_label);
1377                if let Some(condition) = &statement.condition {
1378                    self.emit_expr(condition)?;
1379                    self.emit_branch_zero(end_label)?;
1380                }
1381                self.break_targets.push(end_label);
1382                self.continue_targets.push(update_label);
1383                self.emit_stmt(&statement.body)?;
1384                self.continue_targets.pop();
1385                self.break_targets.pop();
1386                self.compiler.assembler.place_label(update_label);
1387                if let Some(update) = &statement.update {
1388                    self.emit_expr(update)?;
1389                    self.emit_pop_type(&update.ty)?;
1390                }
1391                self.compiler
1392                    .assembler
1393                    .push_jump(NcsOpcode::Jmp, cond_label);
1394                self.compiler.assembler.place_label(end_label);
1395                let end = self.compiler.assembler.new_label();
1396                self.compiler.assembler.place_label(end);
1397                self.compiler.end_line_at(statement.span, end);
1398                Ok(())
1399            }
1400            HirStmt::Case(_) | HirStmt::Default(_) => Err(CodegenError::new(
1401                None,
1402                "case/default labels must be lowered through emit_switch",
1403            )),
1404            HirStmt::Break(span) => {
1405                let start = self.compiler.assembler.new_label();
1406                self.compiler.assembler.place_label(start);
1407                self.compiler.start_line_at(*span, start);
1408                let Some(target) = self.break_targets.last().copied() else {
1409                    return Err(CodegenError::new(
1410                        Some(*span),
1411                        "break used outside loop or switch",
1412                    ));
1413                };
1414                self.compiler.assembler.push_jump(NcsOpcode::Jmp, target);
1415                let end = self.compiler.assembler.new_label();
1416                self.compiler.assembler.place_label(end);
1417                self.compiler.end_line_at(*span, end);
1418                Ok(())
1419            }
1420            HirStmt::Continue(span) => {
1421                let start = self.compiler.assembler.new_label();
1422                self.compiler.assembler.place_label(start);
1423                self.compiler.start_line_at(*span, start);
1424                let Some(target) = self.continue_targets.last().copied() else {
1425                    return Err(CodegenError::new(Some(*span), "continue used outside loop"));
1426                };
1427                self.compiler.assembler.push_jump(NcsOpcode::Jmp, target);
1428                let end = self.compiler.assembler.new_label();
1429                self.compiler.assembler.place_label(end);
1430                self.compiler.end_line_at(*span, end);
1431                Ok(())
1432            }
1433            HirStmt::Empty(span) => {
1434                let start = self.compiler.assembler.new_label();
1435                self.compiler.assembler.place_label(start);
1436                self.compiler.start_line_at(*span, start);
1437                let end = self.compiler.assembler.new_label();
1438                self.compiler.assembler.place_label(end);
1439                self.compiler.end_line_at(*span, end);
1440                Ok(())
1441            }
1442        }
1443    }
1444
1445    fn close_scope_variables(&mut self) {
1446        let Some(indices) = self.scope_stack.pop() else {
1447            return;
1448        };
1449        if indices.is_empty() {
1450            return;
1451        }
1452        let end = self.compiler.assembler.new_label();
1453        self.compiler.assembler.place_label(end);
1454        for index in indices {
1455            if let Some(variable) = self.compiler.variable_debug.get_mut(index) {
1456                variable.end = Some(end);
1457            }
1458        }
1459    }
1460
1461    fn current_scope_variables(&mut self) -> &mut Vec<usize> {
1462        self.scope_stack
1463            .last_mut()
1464            .unwrap_or_else(|| {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("function blocks should always have an active scope")));
}unreachable!("function blocks should always have an active scope"))
1465    }
1466
1467    fn local_info(
1468        &self,
1469        local_id: HirLocalId,
1470        span: crate::Span,
1471    ) -> Result<&crate::HirLocal, CodegenError> {
1472        self.function
1473            .locals
1474            .iter()
1475            .find(|local| local.id == local_id)
1476            .ok_or_else(|| CodegenError::new(Some(span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown local {0:?}", local_id))
    })format!("unknown local {local_id:?}")))
1477    }
1478
1479    fn local_stack_loc(
1480        &self,
1481        local_id: HirLocalId,
1482        span: crate::Span,
1483    ) -> Result<u32, CodegenError> {
1484        let slot = self.layout.locals.get(&local_id).ok_or_else(|| {
1485            CodegenError::new(Some(span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown local slot {0:?}",
                local_id))
    })format!("unknown local slot {local_id:?}"))
1486        })?;
1487        usize_to_u32(slot.offset, "local stack location")
1488    }
1489
1490    fn emit_switch(&mut self, statement: &crate::HirSwitchStmt) -> Result<(), CodegenError> {
1491        let HirStmt::Block(block) = statement.body.as_ref() else {
1492            return Err(CodegenError::new(
1493                Some(statement.span),
1494                "switch lowering requires a block body",
1495            ));
1496        };
1497
1498        let switch_start = self.compiler.assembler.new_label();
1499        self.compiler.assembler.place_label(switch_start);
1500        self.compiler.start_line_at(statement.span, switch_start);
1501        self.emit_expr(&statement.condition)?;
1502        let switch_result_size = size_of_type(&statement.condition.ty, &self.compiler.structs)?;
1503        let body_end = self.compiler.assembler.new_label();
1504        let switch_eval_start = self.compiler.assembler.new_label();
1505        self.compiler.assembler.place_label(switch_eval_start);
1506        self.compiler.variable_debug.push(VariableDebugInfo {
1507            name:      "#switcheval".to_string(),
1508            ty:        SemanticType::Int,
1509            start:     switch_eval_start,
1510            end:       Some(body_end),
1511            stack_loc: usize_to_u32(
1512                self.current_stack_bytes()
1513                    .saturating_sub(switch_result_size),
1514                "switch stack location",
1515            )?,
1516        });
1517
1518        let mut case_labels = Vec::new();
1519        let mut default_label = None;
1520        let mut case_index = 0usize;
1521        for stmt in &block.statements {
1522            match stmt {
1523                HirStmt::Case(case) => {
1524                    case_labels.push((
1525                        case_index,
1526                        evaluate_case_value(case, &self.compiler.constant_env)?,
1527                        self.compiler.assembler.new_label(),
1528                    ));
1529                    case_index += 1;
1530                }
1531                HirStmt::Default(_) => {
1532                    let label = self.compiler.assembler.new_label();
1533                    default_label = Some((case_index, label));
1534                }
1535                _ => {}
1536            }
1537        }
1538
1539        let next_test = self.compiler.assembler.new_label();
1540        self.compiler.assembler.place_label(next_test);
1541        for (_, value, label) in &case_labels {
1542            let after_compare = self.compiler.assembler.new_label();
1543            self.emit_copy_top_value(switch_result_size)?;
1544            emit_push_literal(
1545                self.compiler,
1546                &mut self.temp_bytes,
1547                &Literal::Integer(*value),
1548                &SemanticType::Int,
1549                Some(statement.span),
1550            )?;
1551            self.emit_binary(
1552                BinaryOp::EqualEqual,
1553                &SemanticType::Int,
1554                &SemanticType::Int,
1555                Some(statement.span),
1556            )?;
1557            self.emit_branch_zero(after_compare)?;
1558            self.compiler.assembler.push_jump(NcsOpcode::Jmp, *label);
1559            self.compiler.assembler.place_label(after_compare);
1560        }
1561        if let Some((_, label)) = default_label {
1562            self.compiler.assembler.push_jump(NcsOpcode::Jmp, label);
1563        } else {
1564            self.compiler.assembler.push_jump(NcsOpcode::Jmp, body_end);
1565        }
1566
1567        self.break_targets.push(body_end);
1568        let mut seen_cases = 0usize;
1569        for stmt in &block.statements {
1570            match stmt {
1571                HirStmt::Case(_) => {
1572                    let Some((_, _, label)) = case_labels.get(seen_cases).copied() else {
1573                        return Err(CodegenError::new(
1574                            Some(statement.span),
1575                            "switch case label index out of bounds",
1576                        ));
1577                    };
1578                    seen_cases += 1;
1579                    self.compiler.assembler.place_label(label);
1580                }
1581                HirStmt::Default(span) => {
1582                    let Some((_, label)) = default_label else {
1583                        return Err(CodegenError::new(Some(*span), "missing default label"));
1584                    };
1585                    self.compiler.assembler.place_label(label);
1586                }
1587                other => self.emit_stmt(other)?,
1588            }
1589        }
1590        self.break_targets.pop();
1591        self.compiler.assembler.place_label(body_end);
1592        self.emit_pop_bytes(switch_result_size);
1593        Ok(())
1594    }
1595
1596    fn emit_expr(&mut self, expr: &HirExpr) -> Result<(), CodegenError> {
1597        emit_expr_common(
1598            self.compiler,
1599            &mut self.temp_bytes,
1600            Some(&self.layout),
1601            expr,
1602        )
1603    }
1604
1605    fn emit_store_local(
1606        &mut self,
1607        local: HirLocalId,
1608        span: crate::Span,
1609    ) -> Result<(), CodegenError> {
1610        let layout = self.layout.locals.get(&local).ok_or_else(|| {
1611            CodegenError::new(Some(span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown local slot {0:?}", local))
    })format!("unknown local slot {local:?}"))
1612        })?;
1613        let offset = usize_to_i32(layout.offset, "local slot offset")?
1614            - usize_to_i32(self.current_stack_bytes(), "current stack bytes")?;
1615        self.compiler.assembler.push(NcsInstruction {
1616            opcode:  NcsOpcode::Assignment,
1617            auxcode: NcsAuxCode::TypeVoid,
1618            extra:   assignment_extra(offset, layout.size),
1619        });
1620        Ok(())
1621    }
1622
1623    fn emit_branch_zero(&mut self, target: LabelId) -> Result<(), CodegenError> {
1624        if self.temp_bytes < 4 {
1625            return Err(CodegenError::new(
1626                None,
1627                "branch expected an integer condition on the stack",
1628            ));
1629        }
1630        self.temp_bytes -= 4;
1631        self.compiler.assembler.push_jump(NcsOpcode::Jz, target);
1632        Ok(())
1633    }
1634
1635    fn emit_copy_top_value(&mut self, size: usize) -> Result<(), CodegenError> {
1636        self.compiler.assembler.push(NcsInstruction {
1637            opcode:  NcsOpcode::RunstackCopy,
1638            auxcode: NcsAuxCode::TypeVoid,
1639            extra:   assignment_extra(-usize_to_i32(size, "copy size")?, size),
1640        });
1641        self.temp_bytes += size;
1642        Ok(())
1643    }
1644
1645    fn emit_binary(
1646        &mut self,
1647        op: BinaryOp,
1648        left: &SemanticType,
1649        right: &SemanticType,
1650        span: Option<crate::Span>,
1651    ) -> Result<(), CodegenError> {
1652        let opcode = opcode_for_binary(op);
1653        let auxcode = aux_for_binary(left, right, self.compiler.hir, &self.compiler.structs)?;
1654        let left_size = size_of_type(left, &self.compiler.structs)?;
1655        let right_size = size_of_type(right, &self.compiler.structs)?;
1656        let result_size = size_of_binary_result(op, left, right, &self.compiler.structs)?;
1657        if self.temp_bytes < left_size + right_size {
1658            return Err(CodegenError::new(
1659                span,
1660                "binary operation expected both operands on the stack",
1661            ));
1662        }
1663        self.temp_bytes -= left_size + right_size;
1664        self.temp_bytes += result_size;
1665        let extra = if auxcode == NcsAuxCode::TypeTypeStructStruct
1666            && #[allow(non_exhaustive_omitted_patterns)] match opcode {
    NcsOpcode::Equal | NcsOpcode::NotEqual => true,
    _ => false,
}matches!(opcode, NcsOpcode::Equal | NcsOpcode::NotEqual)
1667        {
1668            usize_to_u16(left_size, "struct equality size")?
1669                .to_be_bytes()
1670                .to_vec()
1671        } else {
1672            Vec::new()
1673        };
1674        self.compiler.assembler.push(NcsInstruction {
1675            opcode,
1676            auxcode,
1677            extra,
1678        });
1679        Ok(())
1680    }
1681
1682    fn emit_pop_type(&mut self, ty: &SemanticType) -> Result<(), CodegenError> {
1683        let size = size_of_type(ty, &self.compiler.structs)?;
1684        self.emit_pop_bytes(size);
1685        Ok(())
1686    }
1687
1688    fn emit_pop_bytes(&mut self, size: usize) {
1689        if size > 0 {
1690            self.temp_bytes = self.temp_bytes.saturating_sub(size);
1691            self.compiler.assembler.push(NcsInstruction {
1692                opcode:  NcsOpcode::ModifyStackPointer,
1693                auxcode: NcsAuxCode::None,
1694                extra:   (-i32::try_from(size).ok().unwrap_or(i32::MAX))
1695                    .to_be_bytes()
1696                    .to_vec(),
1697            });
1698        }
1699    }
1700
1701    fn emit_function_epilogue(&mut self) {
1702        let cleanup = self.layout.locals_size + self.temp_bytes;
1703        if cleanup > 0 {
1704            self.temp_bytes = 0;
1705            self.compiler.assembler.push(NcsInstruction {
1706                opcode:  NcsOpcode::ModifyStackPointer,
1707                auxcode: NcsAuxCode::None,
1708                extra:   (-i32::try_from(cleanup).ok().unwrap_or(i32::MAX))
1709                    .to_be_bytes()
1710                    .to_vec(),
1711            });
1712        }
1713    }
1714
1715    fn current_stack_bytes(&self) -> usize {
1716        let locals = self
1717            .layout
1718            .locals
1719            .values()
1720            .map(|layout| layout.size)
1721            .sum::<usize>();
1722        let params_and_ret = self
1723            .layout
1724            .return_layout
1725            .as_ref()
1726            .map_or(0, |layout| layout.size)
1727            + self
1728                .function
1729                .parameters
1730                .iter()
1731                .map(|parameter| {
1732                    self.layout
1733                        .locals
1734                        .get(&parameter.local)
1735                        .map_or(0, |layout| layout.size)
1736                })
1737                .sum::<usize>();
1738        params_and_ret + locals + self.temp_bytes
1739    }
1740}
1741
1742#[allow(clippy::too_many_lines)]
1743fn emit_expr_common(
1744    compiler: &mut O0Compiler<'_>,
1745    temp_bytes: &mut usize,
1746    layout: Option<&FunctionLayout>,
1747    expr: &HirExpr,
1748) -> Result<(), CodegenError> {
1749    match &expr.kind {
1750        HirExprKind::Literal(literal) => {
1751            emit_push_literal(compiler, temp_bytes, literal, &expr.ty, Some(expr.span))
1752        }
1753        HirExprKind::Value(value) => match value {
1754            crate::HirValueRef::Local(local) => {
1755                let layout = layout.ok_or_else(|| {
1756                    CodegenError::new(Some(expr.span), "local value used outside a function")
1757                })?;
1758                let slot = layout.locals.get(local).ok_or_else(|| {
1759                    CodegenError::new(Some(expr.span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown local slot {0:?}", local))
    })format!("unknown local slot {local:?}"))
1760                })?;
1761                let frame_bytes = function_frame_bytes(layout);
1762                let offset = usize_to_i32(slot.offset, "local load offset")?
1763                    - usize_to_i32(frame_bytes + *temp_bytes, "local frame bytes")?;
1764                compiler.assembler.push(NcsInstruction {
1765                    opcode:  NcsOpcode::RunstackCopy,
1766                    auxcode: NcsAuxCode::TypeVoid,
1767                    extra:   assignment_extra(offset, slot.size),
1768                });
1769                *temp_bytes += slot.size;
1770                Ok(())
1771            }
1772            crate::HirValueRef::Global(name) | crate::HirValueRef::ConstGlobal(name) => {
1773                let slot = compiler.global_layout.get(name).ok_or_else(|| {
1774                    CodegenError::new(Some(expr.span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown global {0:?}", name))
    })format!("unknown global {name:?}"))
1775                })?;
1776                let offset = usize_to_i32(slot.offset, "global load offset")?
1777                    - usize_to_i32(compiler.global_size, "global size")?;
1778                compiler.assembler.push(NcsInstruction {
1779                    opcode:  NcsOpcode::RunstackCopyBase,
1780                    auxcode: NcsAuxCode::TypeVoid,
1781                    extra:   assignment_extra(offset, slot.size),
1782                });
1783                *temp_bytes += slot.size;
1784                Ok(())
1785            }
1786            crate::HirValueRef::BuiltinConstant(name) => {
1787                let value = compiler.builtin_constants.get(name).ok_or_else(|| {
1788                    CodegenError::new(
1789                        Some(expr.span),
1790                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown builtin constant {0:?}",
                name))
    })format!("unknown builtin constant {name:?}"),
1791                    )
1792                })?;
1793                let literal = literal_from_builtin_value(value).ok_or_else(|| {
1794                    CodegenError::new(
1795                        Some(expr.span),
1796                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unsupported builtin constant value for {0:?}",
                name))
    })format!("unsupported builtin constant value for {name:?}"),
1797                    )
1798                })?;
1799                emit_push_literal(compiler, temp_bytes, &literal, &expr.ty, Some(expr.span))
1800            }
1801        },
1802        HirExprKind::Call {
1803            target,
1804            arguments,
1805        } => emit_call(compiler, temp_bytes, layout, expr, target, arguments),
1806        HirExprKind::FieldAccess {
1807            base,
1808            field,
1809        } => {
1810            emit_expr_common(compiler, temp_bytes, layout, base)?;
1811            let base_size = size_of_type(&base.ty, &compiler.structs)?;
1812            let field_layout = field_layout(&base.ty, field, &compiler.structs, Some(expr.span))?;
1813            if true {
    match (&field_layout.ty, &expr.ty) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    };
};debug_assert_eq!(field_layout.ty, expr.ty);
1814            compiler.assembler.push(NcsInstruction {
1815                opcode:  NcsOpcode::RunstackCopy,
1816                auxcode: NcsAuxCode::TypeVoid,
1817                extra:   assignment_extra(
1818                    usize_to_i32(field_layout.offset, "field offset")?
1819                        - usize_to_i32(base_size, "base size")?,
1820                    field_layout.size,
1821                ),
1822            });
1823            *temp_bytes += field_layout.size;
1824            *temp_bytes = temp_bytes.saturating_sub(base_size);
1825            compiler.assembler.push(NcsInstruction {
1826                opcode:  NcsOpcode::ModifyStackPointer,
1827                auxcode: NcsAuxCode::None,
1828                extra:   (-usize_to_i32(base_size, "base size")?)
1829                    .to_be_bytes()
1830                    .to_vec(),
1831            });
1832            Ok(())
1833        }
1834        HirExprKind::Unary {
1835            op,
1836            expr: inner,
1837        } => {
1838            if #[allow(non_exhaustive_omitted_patterns)] match op {
    UnaryOp::PreIncrement | UnaryOp::PreDecrement | UnaryOp::PostIncrement |
        UnaryOp::PostDecrement => true,
    _ => false,
}matches!(
1839                op,
1840                UnaryOp::PreIncrement
1841                    | UnaryOp::PreDecrement
1842                    | UnaryOp::PostIncrement
1843                    | UnaryOp::PostDecrement
1844            ) {
1845                emit_expr_common(compiler, temp_bytes, layout, inner)?;
1846                if #[allow(non_exhaustive_omitted_patterns)] match op {
    UnaryOp::PostIncrement | UnaryOp::PostDecrement => true,
    _ => false,
}matches!(op, UnaryOp::PostIncrement | UnaryOp::PostDecrement) {
1847                    emit_copy_top_bytes(compiler, temp_bytes, 4);
1848                }
1849                emit_push_literal(
1850                    compiler,
1851                    temp_bytes,
1852                    &Literal::Integer(1),
1853                    &SemanticType::Int,
1854                    Some(expr.span),
1855                )?;
1856                let opcode = match op {
1857                    UnaryOp::PreIncrement | UnaryOp::PostIncrement => NcsOpcode::Add,
1858                    UnaryOp::PreDecrement | UnaryOp::PostDecrement => NcsOpcode::Sub,
1859                    _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
1860                };
1861                *temp_bytes = temp_bytes.saturating_sub(8);
1862                *temp_bytes += 4;
1863                compiler.assembler.push(NcsInstruction {
1864                    opcode,
1865                    auxcode: NcsAuxCode::TypeTypeIntegerInteger,
1866                    extra: Vec::new(),
1867                });
1868                emit_store_target(compiler, temp_bytes, layout, inner, expr.span)?;
1869                if #[allow(non_exhaustive_omitted_patterns)] match op {
    UnaryOp::PostIncrement | UnaryOp::PostDecrement => true,
    _ => false,
}matches!(op, UnaryOp::PostIncrement | UnaryOp::PostDecrement) {
1870                    emit_drop_bytes(compiler, temp_bytes, 4);
1871                }
1872                return Ok(());
1873            }
1874            emit_expr_common(compiler, temp_bytes, layout, inner)?;
1875            let opcode = match op {
1876                UnaryOp::Negate => NcsOpcode::Negation,
1877                UnaryOp::OnesComplement => NcsOpcode::OnesComplement,
1878                UnaryOp::BooleanNot => NcsOpcode::BooleanNot,
1879                UnaryOp::PreIncrement
1880                | UnaryOp::PreDecrement
1881                | UnaryOp::PostIncrement
1882                | UnaryOp::PostDecrement => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
1883            };
1884            compiler.assembler.push(NcsInstruction {
1885                opcode,
1886                auxcode: aux_for_unary(&expr.ty, compiler.hir, &compiler.structs)?,
1887                extra: Vec::new(),
1888            });
1889            Ok(())
1890        }
1891        HirExprKind::Binary {
1892            op,
1893            left,
1894            right,
1895        } => {
1896            emit_expr_common(compiler, temp_bytes, layout, left)?;
1897            emit_expr_common(compiler, temp_bytes, layout, right)?;
1898            let opcode = opcode_for_binary(*op);
1899            let aux = aux_for_binary(&left.ty, &right.ty, compiler.hir, &compiler.structs)?;
1900            let left_size = size_of_type(&left.ty, &compiler.structs)?;
1901            let right_size = size_of_type(&right.ty, &compiler.structs)?;
1902            let result_size = size_of_binary_result(*op, &left.ty, &right.ty, &compiler.structs)?;
1903            *temp_bytes = temp_bytes.saturating_sub(left_size + right_size);
1904            *temp_bytes += result_size;
1905            let extra = if aux == NcsAuxCode::TypeTypeStructStruct
1906                && #[allow(non_exhaustive_omitted_patterns)] match opcode {
    NcsOpcode::Equal | NcsOpcode::NotEqual => true,
    _ => false,
}matches!(opcode, NcsOpcode::Equal | NcsOpcode::NotEqual)
1907            {
1908                usize_to_u16(left_size, "struct equality size")?
1909                    .to_be_bytes()
1910                    .to_vec()
1911            } else {
1912                Vec::new()
1913            };
1914            compiler.assembler.push(NcsInstruction {
1915                opcode,
1916                auxcode: aux,
1917                extra,
1918            });
1919            Ok(())
1920        }
1921        HirExprKind::Conditional {
1922            condition,
1923            when_true,
1924            when_false,
1925        } => {
1926            let base_temp_bytes = *temp_bytes;
1927            emit_expr_common(compiler, temp_bytes, layout, condition)?;
1928            if *temp_bytes < base_temp_bytes + 4 {
1929                return Err(CodegenError::new(
1930                    Some(condition.span),
1931                    "conditional expression requires an integer condition",
1932                ));
1933            }
1934
1935            let false_label = compiler.assembler.new_label();
1936            let end_label = compiler.assembler.new_label();
1937            *temp_bytes -= 4;
1938            compiler.assembler.push_jump(NcsOpcode::Jz, false_label);
1939
1940            emit_expr_common(compiler, temp_bytes, layout, when_true)?;
1941            compiler.assembler.push_jump(NcsOpcode::Jmp, end_label);
1942
1943            compiler.assembler.place_label(false_label);
1944            *temp_bytes = base_temp_bytes;
1945            emit_expr_common(compiler, temp_bytes, layout, when_false)?;
1946            compiler.assembler.place_label(end_label);
1947            Ok(())
1948        }
1949        HirExprKind::Assignment {
1950            op,
1951            left,
1952            right,
1953        } => {
1954            if *op == AssignmentOp::Assign {
1955                emit_expr_common(compiler, temp_bytes, layout, right)?;
1956                emit_store_target(compiler, temp_bytes, layout, left, right.span)?;
1957                return Ok(());
1958            }
1959
1960            let binary_op = match op {
1961                AssignmentOp::Assign => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
1962                AssignmentOp::AssignMinus => BinaryOp::Subtract,
1963                AssignmentOp::AssignPlus => BinaryOp::Add,
1964                AssignmentOp::AssignMultiply => BinaryOp::Multiply,
1965                AssignmentOp::AssignDivide => BinaryOp::Divide,
1966                AssignmentOp::AssignModulus => BinaryOp::Modulus,
1967                AssignmentOp::AssignAnd => BinaryOp::BooleanAnd,
1968                AssignmentOp::AssignXor => BinaryOp::ExclusiveOr,
1969                AssignmentOp::AssignOr => BinaryOp::InclusiveOr,
1970                AssignmentOp::AssignShiftLeft => BinaryOp::ShiftLeft,
1971                AssignmentOp::AssignShiftRight => BinaryOp::ShiftRight,
1972                AssignmentOp::AssignUnsignedShiftRight => BinaryOp::UnsignedShiftRight,
1973            };
1974            emit_expr_common(compiler, temp_bytes, layout, left)?;
1975            emit_expr_common(compiler, temp_bytes, layout, right)?;
1976            let aux = aux_for_binary(&left.ty, &right.ty, compiler.hir, &compiler.structs)?;
1977            let left_size = size_of_type(&left.ty, &compiler.structs)?;
1978            let right_size = size_of_type(&right.ty, &compiler.structs)?;
1979            let result_size =
1980                size_of_binary_result(binary_op, &left.ty, &right.ty, &compiler.structs)?;
1981            *temp_bytes = temp_bytes.saturating_sub(left_size + right_size);
1982            *temp_bytes += result_size;
1983            compiler.assembler.push(NcsInstruction {
1984                opcode:  opcode_for_binary(binary_op),
1985                auxcode: aux,
1986                extra:   Vec::new(),
1987            });
1988            emit_store_target(compiler, temp_bytes, layout, left, expr.span)
1989        }
1990    }
1991}
1992
1993fn emit_call(
1994    compiler: &mut O0Compiler<'_>,
1995    temp_bytes: &mut usize,
1996    layout: Option<&FunctionLayout>,
1997    expr: &HirExpr,
1998    target: &HirCallTarget,
1999    arguments: &[HirExpr],
2000) -> Result<(), CodegenError> {
2001    let base_temp = *temp_bytes;
2002    match target {
2003        HirCallTarget::Builtin(name) => {
2004            let (id, function) =
2005                compiler
2006                    .builtin_functions
2007                    .get(name)
2008                    .copied()
2009                    .ok_or_else(|| {
2010                        CodegenError::new(Some(expr.span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown builtin {0:?}", name))
    })format!("unknown builtin {name:?}"))
2011                    })?;
2012            for (index, argument) in arguments.iter().enumerate() {
2013                if function
2014                    .parameters
2015                    .get(index)
2016                    .is_some_and(|parameter| #[allow(non_exhaustive_omitted_patterns)] match parameter.ty {
    BuiltinType::Action => true,
    _ => false,
}matches!(parameter.ty, BuiltinType::Action))
2017                {
2018                    emit_action_parameter(compiler, temp_bytes, layout, argument)?;
2019                    continue;
2020                }
2021                emit_expr_common(compiler, temp_bytes, layout, argument)?;
2022            }
2023            for parameter in function.parameters.iter().skip(arguments.len()) {
2024                if #[allow(non_exhaustive_omitted_patterns)] match parameter.ty {
    BuiltinType::Action => true,
    _ => false,
}matches!(parameter.ty, BuiltinType::Action) {
2025                    let default = parameter.default.as_ref().ok_or_else(|| {
2026                        CodegenError::new(
2027                            Some(expr.span),
2028                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing required parameter for builtin {0:?}",
                name))
    })format!("missing required parameter for builtin {name:?}"),
2029                        )
2030                    })?;
2031                    let action = lower_builtin_action_default_expr(compiler, default, expr.span)?;
2032                    emit_action_parameter(compiler, temp_bytes, layout, &action)?;
2033                    continue;
2034                }
2035                let default = parameter.default.as_ref().ok_or_else(|| {
2036                    CodegenError::new(
2037                        Some(expr.span),
2038                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing required parameter for builtin {0:?}",
                name))
    })format!("missing required parameter for builtin {name:?}"),
2039                    )
2040                })?;
2041                let literal = literal_from_builtin_value(default).ok_or_else(|| {
2042                    CodegenError::new(
2043                        Some(expr.span),
2044                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unsupported builtin default value for {0:?}",
                name))
    })format!("unsupported builtin default value for {name:?}"),
2045                    )
2046                })?;
2047                let ty = semantic_type_from_builtin_type(&parameter.ty);
2048                emit_push_literal(compiler, temp_bytes, &literal, &ty, Some(expr.span))?;
2049            }
2050
2051            let return_size = size_of_type(&expr.ty, &compiler.structs)?;
2052            *temp_bytes = base_temp + return_size;
2053            compiler.assembler.push(NcsInstruction {
2054                opcode:  NcsOpcode::ExecuteCommand,
2055                auxcode: NcsAuxCode::None,
2056                extra:   builtin_call_extra(
2057                    id,
2058                    usize_to_u8(function.parameters.len(), "builtin argc")?,
2059                ),
2060            });
2061            Ok(())
2062        }
2063        HirCallTarget::Function(name) => {
2064            let function = compiler.functions.get(name).copied().ok_or_else(|| {
2065                CodegenError::new(Some(expr.span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown function {0:?}", name))
    })format!("unknown function {name:?}"))
2066            })?;
2067            if function.return_type != SemanticType::Void {
2068                compiler.emit_stack_alloc(&function.return_type)?;
2069                *temp_bytes += size_of_type(&function.return_type, &compiler.structs)?;
2070            }
2071            for argument in arguments {
2072                emit_expr_common(compiler, temp_bytes, layout, argument)?;
2073            }
2074            for parameter in function.parameters.iter().skip(arguments.len()) {
2075                let default = parameter.default.as_ref().ok_or_else(|| {
2076                    CodegenError::new(
2077                        Some(expr.span),
2078                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing required parameter for function {0:?}",
                name))
    })format!("missing required parameter for function {name:?}"),
2079                    )
2080                })?;
2081                emit_expr_common(compiler, temp_bytes, layout, default)?;
2082            }
2083            let label = compiler.function_labels.get(name).copied().ok_or_else(|| {
2084                CodegenError::new(
2085                    Some(expr.span),
2086                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing function label for {0:?}",
                name))
    })format!("missing function label for {name:?}"),
2087                )
2088            })?;
2089            compiler.assembler.push_jump(NcsOpcode::Jsr, label);
2090            let return_size = size_of_type(&function.return_type, &compiler.structs)?;
2091            *temp_bytes = base_temp + return_size;
2092            Ok(())
2093        }
2094    }
2095}
2096
2097fn emit_action_parameter(
2098    compiler: &mut O0Compiler<'_>,
2099    temp_bytes: &mut usize,
2100    layout: Option<&FunctionLayout>,
2101    argument: &HirExpr,
2102) -> Result<(), CodegenError> {
2103    let stack_bytes = layout.map_or(0, function_frame_bytes) + *temp_bytes;
2104
2105    // Upstream emits STORESTATE with aux byte 0x10 before a JMP over the
2106    // embedded action body. Our NCS model does not have a dedicated STORESTATE
2107    // aux variant, so we preserve the raw byte value via the matching enum
2108    // discriminant.
2109    compiler.assembler.push(NcsInstruction {
2110        opcode:  NcsOpcode::StoreState,
2111        auxcode: NcsAuxCode::TypeEngst0,
2112        extra:   store_state_extra(
2113            usize_to_u32(compiler.global_size, "global size")?,
2114            usize_to_u32(stack_bytes, "stack size")?,
2115        ),
2116    });
2117
2118    let action_end = compiler.assembler.new_label();
2119    compiler.assembler.push_jump(NcsOpcode::Jmp, action_end);
2120    emit_expr_common(compiler, temp_bytes, layout, argument)?;
2121    compiler.assembler.push(simple_instruction(NcsOpcode::Ret));
2122    compiler.assembler.place_label(action_end);
2123    Ok(())
2124}
2125
2126fn lower_builtin_action_default_expr(
2127    compiler: &O0Compiler<'_>,
2128    default: &BuiltinValue,
2129    span: crate::Span,
2130) -> Result<HirExpr, CodegenError> {
2131    let BuiltinValue::Raw(raw) = default else {
2132        return Err(CodegenError::new(
2133            Some(span),
2134            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unsupported builtin action default value {0:?}",
                default))
    })format!("unsupported builtin action default value {default:?}"),
2135        ));
2136    };
2137    let langspec = compiler.langspec.ok_or_else(|| {
2138        CodegenError::new(
2139            Some(span),
2140            "builtin action defaults require an active langspec".to_string(),
2141        )
2142    })?;
2143    let synthetic = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("void __nwnrs_builtin_action_default__() {{ {0}; }}",
                raw))
    })format!("void __nwnrs_builtin_action_default__() {{ {raw}; }}");
2144    let script =
2145        parse_text(SourceId::new(u32::MAX - 1), &synthetic, Some(langspec)).map_err(|error| {
2146            CodegenError::new(
2147                Some(span),
2148                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("failed to parse builtin action default {0:?}: {1}",
                raw, error))
    })format!("failed to parse builtin action default {raw:?}: {error}"),
2149            )
2150        })?;
2151    let semantic = analyze_script_with_options(&script, Some(langspec), SemanticOptions::default())
2152        .map_err(|error| {
2153            CodegenError::new(
2154                Some(span),
2155                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("failed to analyze builtin action default {0:?}: {1}",
                raw, error))
    })format!("failed to analyze builtin action default {raw:?}: {error}"),
2156            )
2157        })?;
2158    let hir = lower_to_hir(&script, &semantic, Some(langspec)).map_err(|error| {
2159        CodegenError::new(
2160            Some(span),
2161            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("failed to lower builtin action default {0:?}: {1}",
                raw, error))
    })format!("failed to lower builtin action default {raw:?}: {error}"),
2162        )
2163    })?;
2164    let function = hir.functions.first().ok_or_else(|| {
2165        CodegenError::new(
2166            Some(span),
2167            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("builtin action default {0:?} did not lower to a function body",
                raw))
    })format!("builtin action default {raw:?} did not lower to a function body"),
2168        )
2169    })?;
2170    let body = function.body.as_ref().ok_or_else(|| {
2171        CodegenError::new(
2172            Some(span),
2173            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("builtin action default {0:?} lowered without a function body",
                raw))
    })format!("builtin action default {raw:?} lowered without a function body"),
2174        )
2175    })?;
2176    let statement = body.statements.first().ok_or_else(|| {
2177        CodegenError::new(
2178            Some(span),
2179            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("builtin action default {0:?} lowered to an empty body",
                raw))
    })format!("builtin action default {raw:?} lowered to an empty body"),
2180        )
2181    })?;
2182    match statement {
2183        HirStmt::Expr(expr) => Ok((*expr.clone()).clone()),
2184        _ => Err(CodegenError::new(
2185            Some(span),
2186            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("builtin action default {0:?} must lower to an expression statement",
                raw))
    })format!("builtin action default {raw:?} must lower to an expression statement"),
2187        )),
2188    }
2189}
2190
2191fn emit_store_target(
2192    compiler: &mut O0Compiler<'_>,
2193    temp_bytes: &mut usize,
2194    layout: Option<&FunctionLayout>,
2195    target: &HirExpr,
2196    span: crate::Span,
2197) -> Result<(), CodegenError> {
2198    let resolved = resolve_assignment_target(target, &compiler.structs, Some(span))?;
2199    match resolved.root {
2200        AssignmentTargetRoot::Local(local) => {
2201            let layout = layout.ok_or_else(|| {
2202                CodegenError::new(Some(span), "local assignment used outside a function")
2203            })?;
2204            let slot = layout.locals.get(&local).ok_or_else(|| {
2205                CodegenError::new(Some(span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown local slot {0:?}", local))
    })format!("unknown local slot {local:?}"))
2206            })?;
2207            let offset = usize_to_i32(slot.offset + resolved.offset, "local assignment offset")?
2208                - usize_to_i32(
2209                    function_frame_bytes(layout) + *temp_bytes,
2210                    "local assignment frame size",
2211                )?;
2212            compiler.assembler.push(NcsInstruction {
2213                opcode:  NcsOpcode::Assignment,
2214                auxcode: NcsAuxCode::TypeVoid,
2215                extra:   assignment_extra(offset, resolved.size),
2216            });
2217            Ok(())
2218        }
2219        AssignmentTargetRoot::Global(name) => {
2220            let slot = compiler
2221                .global_layout
2222                .get(name)
2223                .ok_or_else(|| CodegenError::new(Some(span), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown global {0:?}", name))
    })format!("unknown global {name:?}")))?;
2224            let offset = usize_to_i32(slot.offset + resolved.offset, "global assignment offset")?
2225                - usize_to_i32(compiler.global_size, "global size")?;
2226            compiler.assembler.push(NcsInstruction {
2227                opcode:  NcsOpcode::AssignmentBase,
2228                auxcode: NcsAuxCode::TypeVoid,
2229                extra:   assignment_extra(offset, resolved.size),
2230            });
2231            Ok(())
2232        }
2233    }
2234}
2235
2236fn emit_push_literal(
2237    compiler: &mut O0Compiler<'_>,
2238    temp_bytes: &mut usize,
2239    literal: &Literal,
2240    ty: &SemanticType,
2241    span: Option<crate::Span>,
2242) -> Result<(), CodegenError> {
2243    match literal {
2244        Literal::Integer(value) => compiler.assembler.push(NcsInstruction {
2245            opcode:  NcsOpcode::Constant,
2246            auxcode: NcsAuxCode::TypeInteger,
2247            extra:   value.to_be_bytes().to_vec(),
2248        }),
2249        Literal::Float(value) => compiler.assembler.push(NcsInstruction {
2250            opcode:  NcsOpcode::Constant,
2251            auxcode: NcsAuxCode::TypeFloat,
2252            extra:   value.to_bits().to_be_bytes().to_vec(),
2253        }),
2254        Literal::String(value) => compiler.assembler.push(NcsInstruction {
2255            opcode:  NcsOpcode::Constant,
2256            auxcode: NcsAuxCode::TypeString,
2257            extra:   string_extra(value)?,
2258        }),
2259        Literal::ObjectSelf => compiler.assembler.push(NcsInstruction {
2260            opcode:  NcsOpcode::Constant,
2261            auxcode: NcsAuxCode::TypeObject,
2262            extra:   0_i32.to_be_bytes().to_vec(),
2263        }),
2264        Literal::ObjectInvalid => compiler.assembler.push(NcsInstruction {
2265            opcode:  NcsOpcode::Constant,
2266            auxcode: NcsAuxCode::TypeObject,
2267            extra:   1_i32.to_be_bytes().to_vec(),
2268        }),
2269        Literal::LocationInvalid => compiler.assembler.push(NcsInstruction {
2270            opcode:  NcsOpcode::Constant,
2271            auxcode: NcsAuxCode::TypeEngst2,
2272            extra:   0_u32.to_be_bytes().to_vec(),
2273        }),
2274        Literal::Json(value) => compiler.assembler.push(NcsInstruction {
2275            opcode:  NcsOpcode::Constant,
2276            auxcode: NcsAuxCode::TypeEngst7,
2277            extra:   string_extra(value)?,
2278        }),
2279        Literal::Vector(values) => {
2280            for value in values {
2281                compiler.assembler.push(NcsInstruction {
2282                    opcode:  NcsOpcode::Constant,
2283                    auxcode: NcsAuxCode::TypeFloat,
2284                    extra:   value.to_bits().to_be_bytes().to_vec(),
2285                });
2286            }
2287        }
2288        Literal::Magic(magic) => {
2289            let resolved = compiler.magic_literal_value(*magic, span);
2290            return emit_push_literal(compiler, temp_bytes, &resolved, ty, span);
2291        }
2292    }
2293
2294    *temp_bytes += size_of_type(ty, &compiler.structs)?;
2295    Ok(())
2296}
2297
2298fn literal_from_builtin_value(value: &BuiltinValue) -> Option<Literal> {
2299    match value {
2300        BuiltinValue::Int(value) | BuiltinValue::ObjectId(value) => Some(Literal::Integer(*value)),
2301        BuiltinValue::Float(value) => Some(Literal::Float(*value)),
2302        BuiltinValue::String(value) => Some(Literal::String(value.clone())),
2303        BuiltinValue::ObjectSelf => Some(Literal::ObjectSelf),
2304        BuiltinValue::ObjectInvalid => Some(Literal::ObjectInvalid),
2305        BuiltinValue::LocationInvalid => Some(Literal::LocationInvalid),
2306        BuiltinValue::Json(value) => Some(Literal::Json(value.clone())),
2307        BuiltinValue::Vector(value) => Some(Literal::Vector(*value)),
2308        BuiltinValue::Raw(_) => None,
2309    }
2310}
2311
2312fn semantic_type_from_builtin_type(ty: &BuiltinType) -> SemanticType {
2313    match ty {
2314        BuiltinType::Int => SemanticType::Int,
2315        BuiltinType::Float => SemanticType::Float,
2316        BuiltinType::String => SemanticType::String,
2317        BuiltinType::Object => SemanticType::Object,
2318        BuiltinType::Void => SemanticType::Void,
2319        BuiltinType::Action => SemanticType::Action,
2320        BuiltinType::Vector => SemanticType::Vector,
2321        BuiltinType::EngineStructure(name) => SemanticType::EngineStructure(name.clone()),
2322    }
2323}
2324
2325fn evaluate_case_value(
2326    expr: &HirExpr,
2327    constant_env: &BTreeMap<String, ConstValue>,
2328) -> Result<i32, CodegenError> {
2329    match evaluate_const_expr(expr, constant_env) {
2330        Some(ConstValue::Int(value)) => Ok(value),
2331        Some(ConstValue::String(value)) => Ok(nwscript_string_hash(&value)),
2332        Some(ConstValue::Float(_)) | None => Err(CodegenError::new(
2333            Some(expr.span),
2334            "switch case code generation requires a constant int or string",
2335        )),
2336    }
2337}
2338
2339fn function_frame_bytes(layout: &FunctionLayout) -> usize {
2340    layout.locals.values().map(|slot| slot.size).sum::<usize>()
2341        + layout
2342            .return_layout
2343            .as_ref()
2344            .map_or(0, |layout| layout.size)
2345}
2346
2347fn size_of_binary_result(
2348    op: BinaryOp,
2349    left: &SemanticType,
2350    right: &SemanticType,
2351    structs: &BTreeMap<String, &crate::HirStruct>,
2352) -> Result<usize, CodegenError> {
2353    let ty = match op {
2354        BinaryOp::EqualEqual
2355        | BinaryOp::NotEqual
2356        | BinaryOp::GreaterEqual
2357        | BinaryOp::GreaterThan
2358        | BinaryOp::LessThan
2359        | BinaryOp::LessEqual
2360        | BinaryOp::LogicalAnd
2361        | BinaryOp::LogicalOr
2362        | BinaryOp::InclusiveOr
2363        | BinaryOp::ExclusiveOr
2364        | BinaryOp::BooleanAnd
2365        | BinaryOp::ShiftLeft
2366        | BinaryOp::ShiftRight
2367        | BinaryOp::UnsignedShiftRight
2368        | BinaryOp::Modulus => SemanticType::Int,
2369        BinaryOp::Add | BinaryOp::Subtract | BinaryOp::Multiply | BinaryOp::Divide => {
2370            if left == &SemanticType::Float || right == &SemanticType::Float {
2371                if left == &SemanticType::Vector || right == &SemanticType::Vector {
2372                    SemanticType::Vector
2373                } else {
2374                    SemanticType::Float
2375                }
2376            } else if left == &SemanticType::String {
2377                SemanticType::String
2378            } else if left == &SemanticType::Vector {
2379                SemanticType::Vector
2380            } else {
2381                left.clone()
2382            }
2383        }
2384    };
2385    size_of_type(&ty, structs)
2386}
2387
2388fn opcode_for_binary(op: BinaryOp) -> NcsOpcode {
2389    match op {
2390        BinaryOp::Multiply => NcsOpcode::Mul,
2391        BinaryOp::Divide => NcsOpcode::Div,
2392        BinaryOp::Modulus => NcsOpcode::Modulus,
2393        BinaryOp::Add => NcsOpcode::Add,
2394        BinaryOp::Subtract => NcsOpcode::Sub,
2395        BinaryOp::ShiftLeft => NcsOpcode::ShiftLeft,
2396        BinaryOp::ShiftRight => NcsOpcode::ShiftRight,
2397        BinaryOp::UnsignedShiftRight => NcsOpcode::UShiftRight,
2398        BinaryOp::GreaterEqual => NcsOpcode::Geq,
2399        BinaryOp::GreaterThan => NcsOpcode::Gt,
2400        BinaryOp::LessThan => NcsOpcode::Lt,
2401        BinaryOp::LessEqual => NcsOpcode::Leq,
2402        BinaryOp::NotEqual => NcsOpcode::NotEqual,
2403        BinaryOp::EqualEqual => NcsOpcode::Equal,
2404        BinaryOp::BooleanAnd => NcsOpcode::BooleanAnd,
2405        BinaryOp::ExclusiveOr => NcsOpcode::ExclusiveOr,
2406        BinaryOp::InclusiveOr => NcsOpcode::InclusiveOr,
2407        BinaryOp::LogicalAnd => NcsOpcode::LogicalAnd,
2408        BinaryOp::LogicalOr => NcsOpcode::LogicalOr,
2409    }
2410}
2411
2412fn aux_for_binary(
2413    left: &SemanticType,
2414    right: &SemanticType,
2415    hir: &HirModule,
2416    structs: &BTreeMap<String, &crate::HirStruct>,
2417) -> Result<NcsAuxCode, CodegenError> {
2418    match (left, right) {
2419        (SemanticType::Int, SemanticType::Int) => Ok(NcsAuxCode::TypeTypeIntegerInteger),
2420        (SemanticType::Float, SemanticType::Float) => Ok(NcsAuxCode::TypeTypeFloatFloat),
2421        (SemanticType::Object, SemanticType::Object) => Ok(NcsAuxCode::TypeTypeObjectObject),
2422        (SemanticType::String, SemanticType::String) => Ok(NcsAuxCode::TypeTypeStringString),
2423        (SemanticType::Struct(_), SemanticType::Struct(_)) => Ok(NcsAuxCode::TypeTypeStructStruct),
2424        (SemanticType::Int, SemanticType::Float) => Ok(NcsAuxCode::TypeTypeIntegerFloat),
2425        (SemanticType::Float, SemanticType::Int) => Ok(NcsAuxCode::TypeTypeFloatInteger),
2426        (SemanticType::Vector, SemanticType::Vector) => Ok(NcsAuxCode::TypeTypeVectorVector),
2427        (SemanticType::Vector, SemanticType::Float) => Ok(NcsAuxCode::TypeTypeVectorFloat),
2428        (SemanticType::Float, SemanticType::Vector) => Ok(NcsAuxCode::TypeTypeFloatVector),
2429        (SemanticType::EngineStructure(name), SemanticType::EngineStructure(other))
2430            if name == other =>
2431        {
2432            aux_for_engine_structure(name, hir, structs).and_then(|left_aux| match left_aux {
2433                NcsAuxCode::TypeEngst0 => Ok(NcsAuxCode::TypeTypeEngst0Engst0),
2434                NcsAuxCode::TypeEngst1 => Ok(NcsAuxCode::TypeTypeEngst1Engst1),
2435                NcsAuxCode::TypeEngst2 => Ok(NcsAuxCode::TypeTypeEngst2Engst2),
2436                NcsAuxCode::TypeEngst3 => Ok(NcsAuxCode::TypeTypeEngst3Engst3),
2437                NcsAuxCode::TypeEngst4 => Ok(NcsAuxCode::TypeTypeEngst4Engst4),
2438                NcsAuxCode::TypeEngst5 => Ok(NcsAuxCode::TypeTypeEngst5Engst5),
2439                NcsAuxCode::TypeEngst6 => Ok(NcsAuxCode::TypeTypeEngst6Engst6),
2440                NcsAuxCode::TypeEngst7 => Ok(NcsAuxCode::TypeTypeEngst7Engst7),
2441                NcsAuxCode::TypeEngst8 => Ok(NcsAuxCode::TypeTypeEngst8Engst8),
2442                NcsAuxCode::TypeEngst9 => Ok(NcsAuxCode::TypeTypeEngst9Engst9),
2443                _ => Err(CodegenError::new(None, "invalid engine-structure auxcode")),
2444            })
2445        }
2446        _ => Err(CodegenError::new(
2447            None,
2448            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unsupported binary operand pair for code generation: {0:?} and {1:?}",
                left, right))
    })format!("unsupported binary operand pair for code generation: {left:?} and {right:?}"),
2449        )),
2450    }
2451}
2452
2453fn aux_for_unary(
2454    ty: &SemanticType,
2455    hir: &HirModule,
2456    structs: &BTreeMap<String, &crate::HirStruct>,
2457) -> Result<NcsAuxCode, CodegenError> {
2458    match ty {
2459        SemanticType::Int => Ok(NcsAuxCode::TypeInteger),
2460        SemanticType::Float => Ok(NcsAuxCode::TypeFloat),
2461        SemanticType::String => Ok(NcsAuxCode::TypeString),
2462        SemanticType::Object => Ok(NcsAuxCode::TypeObject),
2463        SemanticType::Vector => Ok(NcsAuxCode::TypeTypeVectorVector),
2464        SemanticType::EngineStructure(name) => aux_for_engine_structure(name, hir, structs),
2465        SemanticType::Struct(_) => Ok(NcsAuxCode::TypeTypeStructStruct),
2466        SemanticType::Void | SemanticType::Action => Err(CodegenError::new(
2467            None,
2468            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unsupported unary operand type {0:?}",
                ty))
    })format!("unsupported unary operand type {ty:?}"),
2469        )),
2470    }
2471}
2472
2473fn aux_for_engine_structure(
2474    name: &str,
2475    hir: &HirModule,
2476    _structs: &BTreeMap<String, &crate::HirStruct>,
2477) -> Result<NcsAuxCode, CodegenError> {
2478    let index = hir
2479        .structs
2480        .iter()
2481        .position(|structure| structure.name == name)
2482        .or_else(|| {
2483            [
2484                "effect",
2485                "event",
2486                "location",
2487                "talent",
2488                "itemproperty",
2489                "sqlquery",
2490                "cassowary",
2491                "json",
2492            ]
2493            .iter()
2494            .position(|candidate| *candidate == name)
2495        })
2496        .ok_or_else(|| CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown engine structure {0:?}",
                name))
    })format!("unknown engine structure {name:?}")))?;
2497
2498    Ok(match index {
2499        0 => NcsAuxCode::TypeEngst0,
2500        1 => NcsAuxCode::TypeEngst1,
2501        2 => NcsAuxCode::TypeEngst2,
2502        3 => NcsAuxCode::TypeEngst3,
2503        4 => NcsAuxCode::TypeEngst4,
2504        5 => NcsAuxCode::TypeEngst5,
2505        6 => NcsAuxCode::TypeEngst6,
2506        7 => NcsAuxCode::TypeEngst7,
2507        8 => NcsAuxCode::TypeEngst8,
2508        9 => NcsAuxCode::TypeEngst9,
2509        _ => {
2510            return Err(CodegenError::new(
2511                None,
2512                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("engine structure index out of range for {0:?}",
                name))
    })format!("engine structure index out of range for {name:?}"),
2513            ));
2514        }
2515    })
2516}
2517
2518fn size_of_type(
2519    ty: &SemanticType,
2520    structs: &BTreeMap<String, &crate::HirStruct>,
2521) -> Result<usize, CodegenError> {
2522    match ty {
2523        SemanticType::Void | SemanticType::Action => Ok(0),
2524        SemanticType::Int
2525        | SemanticType::Float
2526        | SemanticType::String
2527        | SemanticType::Object
2528        | SemanticType::EngineStructure(_) => Ok(4),
2529        SemanticType::Vector => Ok(12),
2530        SemanticType::Struct(name) => {
2531            let structure = structs
2532                .get(name)
2533                .ok_or_else(|| CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown structure {0:?}", name))
    })format!("unknown structure {name:?}")))?;
2534            let mut size = 0usize;
2535            for field in &structure.fields {
2536                size += size_of_type(&field.ty, structs)?;
2537            }
2538            Ok(size)
2539        }
2540    }
2541}
2542
2543fn field_layout(
2544    base: &SemanticType,
2545    field: &str,
2546    structs: &BTreeMap<String, &crate::HirStruct>,
2547    span: Option<crate::Span>,
2548) -> Result<FieldLayout, CodegenError> {
2549    match base {
2550        SemanticType::Vector => {
2551            let offset = match field {
2552                "x" => 0,
2553                "y" => 4,
2554                "z" => 8,
2555                _ => {
2556                    return Err(CodegenError::new(
2557                        span,
2558                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("field {0:?} does not exist on vector",
                field))
    })format!("field {field:?} does not exist on vector"),
2559                    ));
2560                }
2561            };
2562            Ok(FieldLayout {
2563                ty: SemanticType::Float,
2564                offset,
2565                size: 4,
2566            })
2567        }
2568        SemanticType::Struct(name) => {
2569            let structure = structs
2570                .get(name)
2571                .ok_or_else(|| CodegenError::new(span, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown structure {0:?}", name))
    })format!("unknown structure {name:?}")))?;
2572            let mut offset = 0usize;
2573            for candidate in &structure.fields {
2574                let size = size_of_type(&candidate.ty, structs)?;
2575                if candidate.name == field {
2576                    return Ok(FieldLayout {
2577                        ty: candidate.ty.clone(),
2578                        offset,
2579                        size,
2580                    });
2581                }
2582                offset += size;
2583            }
2584            Err(CodegenError::new(
2585                span,
2586                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("field {0:?} does not exist on structure {1:?}",
                field, name))
    })format!("field {field:?} does not exist on structure {name:?}"),
2587            ))
2588        }
2589        _ => Err(CodegenError::new(
2590            span,
2591            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("field access requires a vector or struct base, got {0:?}",
                base))
    })format!("field access requires a vector or struct base, got {base:?}"),
2592        )),
2593    }
2594}
2595
2596enum AssignmentTargetRoot<'a> {
2597    Local(HirLocalId),
2598    Global(&'a str),
2599}
2600
2601struct AssignmentTarget<'a> {
2602    root:   AssignmentTargetRoot<'a>,
2603    offset: usize,
2604    size:   usize,
2605}
2606
2607fn resolve_assignment_target<'a>(
2608    target: &'a HirExpr,
2609    structs: &BTreeMap<String, &'a crate::HirStruct>,
2610    span: Option<crate::Span>,
2611) -> Result<AssignmentTarget<'a>, CodegenError> {
2612    match &target.kind {
2613        HirExprKind::Value(crate::HirValueRef::Local(local)) => Ok(AssignmentTarget {
2614            root:   AssignmentTargetRoot::Local(*local),
2615            offset: 0,
2616            size:   size_of_type(&target.ty, structs)?,
2617        }),
2618        HirExprKind::Value(
2619            crate::HirValueRef::Global(name) | crate::HirValueRef::ConstGlobal(name),
2620        ) => Ok(AssignmentTarget {
2621            root:   AssignmentTargetRoot::Global(name),
2622            offset: 0,
2623            size:   size_of_type(&target.ty, structs)?,
2624        }),
2625        HirExprKind::FieldAccess {
2626            base,
2627            field,
2628        } => {
2629            let mut resolved = resolve_assignment_target(base, structs, span)?;
2630            let field_layout = field_layout(&base.ty, field, structs, span)?;
2631            resolved.offset += field_layout.offset;
2632            resolved.size = field_layout.size;
2633            Ok(resolved)
2634        }
2635        _ => Err(CodegenError::new(
2636            span,
2637            "assignment target code generation is not implemented yet",
2638        )),
2639    }
2640}
2641
2642fn string_extra(value: &str) -> Result<Vec<u8>, CodegenError> {
2643    let length = u16::try_from(value.len()).map_err(|_error| {
2644        CodegenError::new(None, "string constant exceeds NCS 16-bit length limit")
2645    })?;
2646    let mut bytes = Vec::with_capacity(2 + value.len());
2647    bytes.extend_from_slice(&length.to_be_bytes());
2648    bytes.extend_from_slice(value.as_bytes());
2649    Ok(bytes)
2650}
2651
2652fn format_magic_date(timestamp: SystemTime) -> String {
2653    let seconds = timestamp
2654        .duration_since(UNIX_EPOCH)
2655        .unwrap_or(Duration::ZERO)
2656        .as_secs();
2657    let days = i64::try_from(seconds / 86_400).ok().unwrap_or(i64::MAX);
2658    let (year, month, day) = civil_from_days(days);
2659    let month_name = match month {
2660        2 => "Feb",
2661        3 => "Mar",
2662        4 => "Apr",
2663        5 => "May",
2664        6 => "Jun",
2665        7 => "Jul",
2666        8 => "Aug",
2667        9 => "Sep",
2668        10 => "Oct",
2669        11 => "Nov",
2670        12 => "Dec",
2671        _ => "Jan",
2672    };
2673    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} {1:02} {2:04}", month_name,
                day, year))
    })format!("{month_name} {day:02} {year:04}")
2674}
2675
2676fn format_magic_time(timestamp: SystemTime) -> String {
2677    let seconds = timestamp
2678        .duration_since(UNIX_EPOCH)
2679        .unwrap_or(Duration::ZERO)
2680        .as_secs();
2681    let seconds_of_day = seconds % 86_400;
2682    let hour = seconds_of_day / 3_600;
2683    let minute = (seconds_of_day % 3_600) / 60;
2684    let second = seconds_of_day % 60;
2685    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:02}:{1:02}:{2:02}", hour,
                minute, second))
    })format!("{hour:02}:{minute:02}:{second:02}")
2686}
2687
2688fn civil_from_days(days_since_epoch: i64) -> (i32, u32, u32) {
2689    let z = days_since_epoch + 719_468;
2690    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
2691    let doe = z - era * 146_097;
2692    let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
2693    let y = yoe + era * 400;
2694    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2695    let mp = (5 * doy + 2) / 153;
2696    let d = doy - (153 * mp + 2) / 5 + 1;
2697    let m = mp + if mp < 10 { 3 } else { -9 };
2698    let year = y + i64::from(m <= 2);
2699    (
2700        i32::try_from(year).ok().unwrap_or(i32::MAX),
2701        u32::try_from(m).ok().unwrap_or(1),
2702        u32::try_from(d).ok().unwrap_or(1),
2703    )
2704}
2705
2706fn assignment_extra(offset: i32, size: usize) -> Vec<u8> {
2707    let mut bytes = Vec::with_capacity(6);
2708    bytes.extend_from_slice(&offset.to_be_bytes());
2709    let size = usize_to_u16(size, "assignment size")
2710        .ok()
2711        .unwrap_or(u16::MAX);
2712    bytes.extend_from_slice(&size.to_be_bytes());
2713    bytes
2714}
2715
2716fn builtin_call_extra(id: u16, argc: u8) -> Vec<u8> {
2717    let mut bytes = Vec::with_capacity(3);
2718    bytes.extend_from_slice(&id.to_be_bytes());
2719    bytes.push(argc);
2720    bytes
2721}
2722
2723fn store_state_extra(global_size: u32, stack_size: u32) -> Vec<u8> {
2724    let mut bytes = Vec::with_capacity(8);
2725    bytes.extend_from_slice(&global_size.to_be_bytes());
2726    bytes.extend_from_slice(&stack_size.to_be_bytes());
2727    bytes
2728}
2729
2730fn emit_copy_top_bytes(compiler: &mut O0Compiler<'_>, temp_bytes: &mut usize, size: usize) {
2731    compiler.assembler.push(NcsInstruction {
2732        opcode:  NcsOpcode::RunstackCopy,
2733        auxcode: NcsAuxCode::TypeVoid,
2734        extra:   assignment_extra(-i32::try_from(size).ok().unwrap_or(i32::MAX), size),
2735    });
2736    *temp_bytes += size;
2737}
2738
2739fn emit_drop_bytes(compiler: &mut O0Compiler<'_>, temp_bytes: &mut usize, size: usize) {
2740    if size > 0 {
2741        *temp_bytes = temp_bytes.saturating_sub(size);
2742        compiler.assembler.push(NcsInstruction {
2743            opcode:  NcsOpcode::ModifyStackPointer,
2744            auxcode: NcsAuxCode::None,
2745            extra:   (-i32::try_from(size).ok().unwrap_or(i32::MAX))
2746                .to_be_bytes()
2747                .to_vec(),
2748        });
2749    }
2750}
2751
2752fn build_ndb(
2753    hir: &HirModule,
2754    langspec: Option<&LangSpec>,
2755    source_map: &SourceMap,
2756    root_id: SourceId,
2757    output: &CodegenOutput,
2758) -> Result<Ndb, CodegenError> {
2759    let mut lines = Vec::new();
2760    let mut file_order = Vec::new();
2761    let mut file_indices = BTreeMap::new();
2762
2763    for line in &output.lines {
2764        let Some(file) = source_map.get(line.source_id) else {
2765            continue;
2766        };
2767        let file_num = if let Some(file_num) = file_indices.get(&file.id).copied() {
2768            file_num
2769        } else {
2770            let file_num = file_order.len();
2771            file_order.push(file.id);
2772            file_indices.insert(file.id, file_num);
2773            file_num
2774        };
2775        lines.push(NdbLine {
2776            file_num,
2777            line_num: line.line_num,
2778            binary_start: output
2779                .label_offsets
2780                .get(&line.start)
2781                .copied()
2782                .unwrap_or_default(),
2783            binary_end: output
2784                .label_offsets
2785                .get(&line.end)
2786                .copied()
2787                .unwrap_or_default(),
2788        });
2789    }
2790
2791    if !file_indices.contains_key(&root_id)
2792        && let Some(root) = source_map.get(root_id)
2793    {
2794        file_indices.insert(root_id, file_order.len());
2795        file_order.push(root.id);
2796    }
2797
2798    let files = file_order
2799        .into_iter()
2800        .filter_map(|file_id| {
2801            source_map.get(file_id).map(|file| NdbFile {
2802                name:    file.name.clone(),
2803                is_root: file.id == root_id,
2804            })
2805        })
2806        .collect::<Vec<_>>();
2807
2808    let structs = hir
2809        .structs
2810        .iter()
2811        .map(|structure| {
2812            Ok::<_, CodegenError>(NdbStruct {
2813                label:  structure.name.clone(),
2814                fields: structure
2815                    .fields
2816                    .iter()
2817                    .map(|field| {
2818                        Ok(NdbStructField {
2819                            label: field.name.clone(),
2820                            ty:    debug_type_for_semantic(&field.ty, hir, langspec)?,
2821                        })
2822                    })
2823                    .collect::<Result<Vec<_>, CodegenError>>()?,
2824            })
2825        })
2826        .collect::<Result<Vec<_>, _>>()?;
2827
2828    let functions = hir
2829        .functions
2830        .iter()
2831        .filter(|function| !function.is_builtin)
2832        .map(|function| {
2833            let info = output.functions.get(&function.name).ok_or_else(|| {
2834                CodegenError::new(
2835                    Some(function.span),
2836                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("missing debug range for {0:?}",
                function.name))
    })format!("missing debug range for {:?}", function.name),
2837                )
2838            })?;
2839            Ok::<_, CodegenError>(NdbFunction {
2840                label:        function.name.clone(),
2841                binary_start: output
2842                    .label_offsets
2843                    .get(&info.start)
2844                    .copied()
2845                    .unwrap_or_default(),
2846                binary_end:   output
2847                    .label_offsets
2848                    .get(&info.end)
2849                    .copied()
2850                    .unwrap_or_default(),
2851                return_type:  debug_type_for_semantic(&function.return_type, hir, langspec)?,
2852                args:         function
2853                    .parameters
2854                    .iter()
2855                    .map(|parameter| debug_type_for_semantic(&parameter.ty, hir, langspec))
2856                    .collect::<Result<Vec<_>, _>>()?,
2857            })
2858        })
2859        .collect::<Result<Vec<_>, _>>()?;
2860
2861    let variables = output
2862        .variables
2863        .iter()
2864        .map(|variable| {
2865            Ok::<_, CodegenError>(NdbVariable {
2866                label:        variable.name.clone(),
2867                ty:           debug_type_for_semantic(&variable.ty, hir, langspec)?,
2868                binary_start: output
2869                    .label_offsets
2870                    .get(&variable.start)
2871                    .copied()
2872                    .unwrap_or_default(),
2873                binary_end:   variable
2874                    .end
2875                    .and_then(|end| output.label_offsets.get(&end).copied())
2876                    .unwrap_or(u32::MAX),
2877                stack_loc:    variable.stack_loc,
2878            })
2879        })
2880        .collect::<Result<Vec<_>, CodegenError>>()?;
2881
2882    Ok(Ndb {
2883        files,
2884        structs,
2885        functions,
2886        variables,
2887        lines,
2888    })
2889}
2890
2891fn debug_type_for_semantic(
2892    ty: &SemanticType,
2893    hir: &HirModule,
2894    langspec: Option<&LangSpec>,
2895) -> Result<NdbType, CodegenError> {
2896    Ok(match ty {
2897        SemanticType::Float => NdbType::Float,
2898        SemanticType::Int => NdbType::Int,
2899        SemanticType::Void => NdbType::Void,
2900        SemanticType::Object => NdbType::Object,
2901        SemanticType::String => NdbType::String,
2902        SemanticType::EngineStructure(name) => {
2903            NdbType::EngineStructure(engine_structure_index(name, langspec)?)
2904        }
2905        SemanticType::Struct(name) => {
2906            let index = hir
2907                .structs
2908                .iter()
2909                .position(|structure| structure.name == *name)
2910                .ok_or_else(|| {
2911                    CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown debug structure {0:?}",
                name))
    })format!("unknown debug structure {name:?}"))
2912                })?;
2913            NdbType::Struct(index)
2914        }
2915        SemanticType::Vector | SemanticType::Action => NdbType::Unknown,
2916    })
2917}
2918
2919fn engine_structure_index(name: &str, langspec: Option<&LangSpec>) -> Result<u8, CodegenError> {
2920    if let Some(langspec) = langspec
2921        && let Some(index) = langspec
2922            .engine_structures
2923            .iter()
2924            .position(|candidate| candidate.eq_ignore_ascii_case(name))
2925    {
2926        return u8::try_from(index).map_err(|_error| {
2927            CodegenError::new(
2928                None,
2929                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("engine structure index out of range for {0:?}",
                name))
    })format!("engine structure index out of range for {name:?}"),
2930            )
2931        });
2932    }
2933
2934    let fallback = [
2935        "effect",
2936        "event",
2937        "location",
2938        "talent",
2939        "itemproperty",
2940        "sqlquery",
2941        "cassowary",
2942        "json",
2943        "vector",
2944    ];
2945    fallback
2946        .iter()
2947        .position(|candidate| candidate.eq_ignore_ascii_case(name))
2948        .and_then(|index| u8::try_from(index).ok())
2949        .ok_or_else(|| CodegenError::new(None, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown engine structure {0:?}",
                name))
    })format!("unknown engine structure {name:?}")))
2950}
2951
2952fn simple_instruction(opcode: NcsOpcode) -> NcsInstruction {
2953    NcsInstruction {
2954        opcode,
2955        auxcode: NcsAuxCode::None,
2956        extra: Vec::new(),
2957    }
2958}
2959
2960fn simple_aux_instruction(opcode: NcsOpcode, auxcode: NcsAuxCode) -> NcsInstruction {
2961    NcsInstruction {
2962        opcode,
2963        auxcode,
2964        extra: Vec::new(),
2965    }
2966}
2967
2968#[cfg(test)]
2969mod tests {
2970    use super::{CompileOptions, OptimizationLevel};
2971    use crate::{
2972        BuiltinConstant, BuiltinFunction, BuiltinParameter, BuiltinType, BuiltinValue, NcsAuxCode,
2973        NcsOpcode, SourceId, SourceMap, compile_script, compile_script_with_source_map,
2974        decode_ncs_instructions, parse_text, read_ndb,
2975    };
2976
2977    fn decode_string_constant(extra: &[u8]) -> String {
2978        let length_bytes: [u8; 2] = extra
2979            .get(..2)
2980            .expect("string constant should include a 16-bit length prefix")
2981            .try_into()
2982            .expect("string constant length prefix should be two bytes");
2983        let length = u16::from_be_bytes(length_bytes) as usize;
2984        let payload = extra
2985            .get(2..2 + length)
2986            .expect("string constant payload should match its encoded length");
2987        String::from_utf8(payload.to_vec()).expect("string constant should be utf-8")
2988    }
2989
2990    fn decode_integer_constant(extra: &[u8]) -> i32 {
2991        let bytes: [u8; 4] = extra
2992            .get(..4)
2993            .expect("integer constant should encode four bytes")
2994            .try_into()
2995            .expect("integer constant prefix should be four bytes");
2996        i32::from_be_bytes(bytes)
2997    }
2998
2999    fn test_langspec() -> crate::LangSpec {
3000        crate::LangSpec {
3001            engine_num_structures: 3,
3002            engine_structures:     vec![
3003                "effect".to_string(),
3004                "location".to_string(),
3005                "json".to_string(),
3006            ],
3007            constants:             vec![
3008                BuiltinConstant {
3009                    name:  "TRUE".to_string(),
3010                    ty:    BuiltinType::Int,
3011                    value: BuiltinValue::Int(1),
3012                },
3013                BuiltinConstant {
3014                    name:  "FALSE".to_string(),
3015                    ty:    BuiltinType::Int,
3016                    value: BuiltinValue::Int(0),
3017                },
3018                BuiltinConstant {
3019                    name:  "OBJECT_SELF".to_string(),
3020                    ty:    BuiltinType::Object,
3021                    value: BuiltinValue::ObjectSelf,
3022                },
3023                BuiltinConstant {
3024                    name:  "OBJECT_INVALID".to_string(),
3025                    ty:    BuiltinType::Object,
3026                    value: BuiltinValue::ObjectInvalid,
3027                },
3028                BuiltinConstant {
3029                    name:  "OBJECT_TYPE_CREATURE".to_string(),
3030                    ty:    BuiltinType::Int,
3031                    value: BuiltinValue::Int(1),
3032                },
3033            ],
3034            functions:             vec![
3035                BuiltinFunction {
3036                    name:        "GetCurrentHitPoints".to_string(),
3037                    return_type: BuiltinType::Int,
3038                    parameters:  vec![],
3039                },
3040                BuiltinFunction {
3041                    name:        "GetMaxHitPoints".to_string(),
3042                    return_type: BuiltinType::Int,
3043                    parameters:  vec![],
3044                },
3045                BuiltinFunction {
3046                    name:        "CreateObject".to_string(),
3047                    return_type: BuiltinType::Object,
3048                    parameters:  vec![
3049                        BuiltinParameter {
3050                            name:    "nObjectType".to_string(),
3051                            ty:      BuiltinType::Int,
3052                            default: None,
3053                        },
3054                        BuiltinParameter {
3055                            name:    "sTemplate".to_string(),
3056                            ty:      BuiltinType::String,
3057                            default: None,
3058                        },
3059                        BuiltinParameter {
3060                            name:    "lLocation".to_string(),
3061                            ty:      BuiltinType::EngineStructure("location".to_string()),
3062                            default: None,
3063                        },
3064                    ],
3065                },
3066                BuiltinFunction {
3067                    name:        "GetLocation".to_string(),
3068                    return_type: BuiltinType::EngineStructure("location".to_string()),
3069                    parameters:  vec![BuiltinParameter {
3070                        name:    "oTarget".to_string(),
3071                        ty:      BuiltinType::Object,
3072                        default: None,
3073                    }],
3074                },
3075                BuiltinFunction {
3076                    name:        "SetListening".to_string(),
3077                    return_type: BuiltinType::Void,
3078                    parameters:  vec![
3079                        BuiltinParameter {
3080                            name:    "oTarget".to_string(),
3081                            ty:      BuiltinType::Object,
3082                            default: None,
3083                        },
3084                        BuiltinParameter {
3085                            name:    "bValue".to_string(),
3086                            ty:      BuiltinType::Int,
3087                            default: None,
3088                        },
3089                    ],
3090                },
3091                BuiltinFunction {
3092                    name:        "SetListenPattern".to_string(),
3093                    return_type: BuiltinType::Void,
3094                    parameters:  vec![
3095                        BuiltinParameter {
3096                            name:    "oTarget".to_string(),
3097                            ty:      BuiltinType::Object,
3098                            default: None,
3099                        },
3100                        BuiltinParameter {
3101                            name:    "sPattern".to_string(),
3102                            ty:      BuiltinType::String,
3103                            default: None,
3104                        },
3105                        BuiltinParameter {
3106                            name:    "nNumber".to_string(),
3107                            ty:      BuiltinType::Int,
3108                            default: None,
3109                        },
3110                    ],
3111                },
3112                BuiltinFunction {
3113                    name:        "SpeakString".to_string(),
3114                    return_type: BuiltinType::Void,
3115                    parameters:  vec![BuiltinParameter {
3116                        name:    "sMessage".to_string(),
3117                        ty:      BuiltinType::String,
3118                        default: None,
3119                    }],
3120                },
3121                BuiltinFunction {
3122                    name:        "DelayCommand".to_string(),
3123                    return_type: BuiltinType::Void,
3124                    parameters:  vec![
3125                        BuiltinParameter {
3126                            name:    "fSeconds".to_string(),
3127                            ty:      BuiltinType::Float,
3128                            default: None,
3129                        },
3130                        BuiltinParameter {
3131                            name:    "aAction".to_string(),
3132                            ty:      BuiltinType::Action,
3133                            default: Some(BuiltinValue::Raw(
3134                                "SpeakString(\"default action\")".to_string(),
3135                            )),
3136                        },
3137                    ],
3138                },
3139            ],
3140        }
3141    }
3142
3143    #[test]
3144    fn compiles_conditional_script_to_valid_ncs() -> Result<(), Box<dyn std::error::Error>> {
3145        let script = parse_text(
3146            SourceId::new(80),
3147            r#"
3148                int StartingConditional() {
3149                    int nCurHP = GetCurrentHitPoints();
3150                    int nMaxHP = GetMaxHitPoints();
3151                    if (nCurHP < (nMaxHP / 4)) {
3152                        return TRUE;
3153                    }
3154                    return FALSE;
3155                }
3156            "#,
3157            Some(&test_langspec()),
3158        )?;
3159
3160        let artifacts = compile_script(
3161            &script,
3162            Some(&test_langspec()),
3163            CompileOptions {
3164                optimization: OptimizationLevel::O0,
3165                ..CompileOptions::default()
3166            },
3167        )?;
3168        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3169
3170        assert!(!instructions.is_empty());
3171        assert_eq!(
3172            instructions.first().map(|instruction| instruction.opcode),
3173            Some(NcsOpcode::RunstackAdd)
3174        );
3175        assert!(
3176            instructions
3177                .iter()
3178                .any(|instruction| instruction.opcode == NcsOpcode::Jsr)
3179        );
3180        assert!(
3181            instructions
3182                .iter()
3183                .any(|instruction| instruction.opcode == NcsOpcode::Ret)
3184        );
3185        Ok(())
3186    }
3187
3188    #[test]
3189    fn compiles_builtin_action_defaults() -> Result<(), Box<dyn std::error::Error>> {
3190        let script = parse_text(
3191            SourceId::new(95),
3192            r#"
3193                void main() {
3194                    DelayCommand(1.0);
3195                }
3196            "#,
3197            Some(&test_langspec()),
3198        )?;
3199
3200        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3201        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3202
3203        assert!(
3204            instructions
3205                .iter()
3206                .any(|instruction| instruction.opcode == NcsOpcode::StoreState),
3207            "builtin action defaults should emit an embedded action body"
3208        );
3209        assert!(
3210            instructions.iter().any(|instruction| {
3211                instruction.opcode == NcsOpcode::Constant
3212                    && instruction.auxcode == NcsAuxCode::TypeString
3213                    && decode_string_constant(&instruction.extra) == "default action"
3214            }),
3215            "builtin action default body should be compiled from its raw langspec expression"
3216        );
3217
3218        Ok(())
3219    }
3220
3221    #[test]
3222    fn compiles_simple_user_and_builtin_calls_to_valid_ncs()
3223    -> Result<(), Box<dyn std::error::Error>> {
3224        let script = parse_text(
3225            SourceId::new(81),
3226            r#"
3227                void SetupListening(object oCheater) {
3228                    SetListening(oCheater, TRUE);
3229                    SetListenPattern(oCheater, "1", 1001);
3230                }
3231                void main() {
3232                    object oCheater = CreateObject(OBJECT_TYPE_CREATURE, "x0_cheater", GetLocation(OBJECT_SELF));
3233                    SetupListening(oCheater);
3234                }
3235            "#,
3236            Some(&test_langspec()),
3237        )?;
3238
3239        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3240        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3241
3242        assert!(
3243            instructions
3244                .iter()
3245                .any(|instruction| instruction.opcode == NcsOpcode::ExecuteCommand)
3246        );
3247        assert!(
3248            instructions
3249                .iter()
3250                .filter(|instruction| instruction.opcode == NcsOpcode::Jsr)
3251                .count()
3252                >= 2
3253        );
3254        Ok(())
3255    }
3256
3257    #[test]
3258    fn compiles_user_defined_optional_parameter_defaults() -> Result<(), Box<dyn std::error::Error>>
3259    {
3260        let script = parse_text(
3261            SourceId::new(82),
3262            r#"
3263                int AddOne(int nBase = TRUE) {
3264                    return nBase + 1;
3265                }
3266                int StartingConditional() {
3267                    return AddOne();
3268                }
3269            "#,
3270            Some(&test_langspec()),
3271        )?;
3272
3273        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3274        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3275
3276        assert!(
3277            instructions
3278                .iter()
3279                .any(|instruction| instruction.opcode == NcsOpcode::Jsr)
3280        );
3281        assert!(
3282            instructions
3283                .iter()
3284                .any(|instruction| instruction.opcode == NcsOpcode::Constant
3285                    && instruction.auxcode == crate::NcsAuxCode::TypeInteger
3286                    && instruction.extra == 1_i32.to_be_bytes().to_vec()),
3287            "default integer argument should be materialized before the user call"
3288        );
3289        Ok(())
3290    }
3291
3292    #[test]
3293    fn compiles_calls_using_defaults_from_forward_declarations()
3294    -> Result<(), Box<dyn std::error::Error>> {
3295        let script = parse_text(
3296            SourceId::new(84),
3297            r#"
3298                void helper(object oTarget = OBJECT_INVALID);
3299                void helper(object oTarget) {}
3300                void main() {
3301                    helper();
3302                }
3303            "#,
3304            Some(&test_langspec()),
3305        )?;
3306
3307        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3308        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3309
3310        assert!(
3311            instructions
3312                .iter()
3313                .any(|instruction| instruction.opcode == NcsOpcode::Jsr)
3314        );
3315        Ok(())
3316    }
3317
3318    #[test]
3319    fn non_void_returns_jump_to_a_shared_function_exit() -> Result<(), Box<dyn std::error::Error>> {
3320        let script = parse_text(
3321            SourceId::new(85),
3322            r#"
3323                int StartingConditional() {
3324                    if (TRUE) {
3325                        return TRUE;
3326                    }
3327                    return FALSE;
3328                }
3329            "#,
3330            Some(&test_langspec()),
3331        )?;
3332
3333        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3334        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3335
3336        assert_eq!(
3337            instructions
3338                .iter()
3339                .filter(|instruction| instruction.opcode == NcsOpcode::Ret)
3340                .count(),
3341            2,
3342            "the module should only emit one loader RET and one shared function-exit RET",
3343        );
3344        assert!(
3345            instructions
3346                .iter()
3347                .filter(|instruction| instruction.opcode == NcsOpcode::Jmp)
3348                .count()
3349                >= 2,
3350            "non-void return statements should branch to the shared function exit",
3351        );
3352        Ok(())
3353    }
3354
3355    #[test]
3356    fn ndb_includes_upstream_debugger_synthetic_retvals() {
3357        let source = br#"
3358            int StartingConditional() {
3359                return TRUE;
3360            }
3361        "#;
3362        let mut source_map = SourceMap::new();
3363        let root_id = source_map.add_file("synthetic_retval.nss".to_string(), source.to_vec());
3364        let script = parse_text(
3365            root_id,
3366            std::str::from_utf8(source).expect("utf-8"),
3367            Some(&test_langspec()),
3368        )
3369        .expect("script should parse");
3370
3371        let artifacts = compile_script_with_source_map(
3372            &script,
3373            &source_map,
3374            root_id,
3375            Some(&test_langspec()),
3376            CompileOptions::default(),
3377        )
3378        .expect("compile should succeed");
3379        let ndb = read_ndb(&mut std::io::Cursor::new(
3380            artifacts.ndb.expect("NDB output should be present"),
3381        ))
3382        .expect("NDB should parse");
3383
3384        assert!(
3385            ndb.variables
3386                .iter()
3387                .any(|variable| variable.label == "#retval"),
3388            "non-void entrypoint should preserve #retval debug records",
3389        );
3390        let loader_retval = ndb
3391            .variables
3392            .iter()
3393            .find(|variable| variable.label == "#retval" && variable.binary_end == u32::MAX)
3394            .expect("loader #retval debug record should be present");
3395        assert_eq!(
3396            loader_retval.binary_start, 2,
3397            "loader #retval should begin after the loader RunstackAdd instruction",
3398        );
3399    }
3400
3401    #[test]
3402    fn ndb_tracks_switch_eval_and_block_local_lifetimes() {
3403        let source = br#"
3404            void main() {
3405                switch (TRUE) {
3406                    case TRUE:
3407                    {
3408                        int nLocal = 1;
3409                        break;
3410                    }
3411                    default:
3412                        break;
3413                }
3414            }
3415        "#;
3416        let mut source_map = SourceMap::new();
3417        let root_id = source_map.add_file("switch_debug.nss".to_string(), source.to_vec());
3418        let script = parse_text(
3419            root_id,
3420            std::str::from_utf8(source).expect("utf-8"),
3421            Some(&test_langspec()),
3422        )
3423        .expect("script should parse");
3424
3425        let artifacts = compile_script_with_source_map(
3426            &script,
3427            &source_map,
3428            root_id,
3429            Some(&test_langspec()),
3430            CompileOptions::default(),
3431        )
3432        .expect("compile should succeed");
3433        let ndb = read_ndb(&mut std::io::Cursor::new(
3434            artifacts.ndb.expect("NDB output should be present"),
3435        ))
3436        .expect("NDB should parse");
3437
3438        let switch_eval = ndb
3439            .variables
3440            .iter()
3441            .find(|variable| variable.label == "#switcheval")
3442            .expect("switch debug variable should be present");
3443        let local = ndb
3444            .variables
3445            .iter()
3446            .find(|variable| variable.label == "nLocal")
3447            .expect("block local should be present");
3448
3449        assert!(
3450            switch_eval.binary_end >= local.binary_end,
3451            "switch eval lifetime should cover the switch body",
3452        );
3453        assert!(
3454            local.binary_start > switch_eval.binary_start,
3455            "block local should start after switch evaluation begins",
3456        );
3457    }
3458
3459    #[test]
3460    fn ndb_global_debug_starts_after_global_allocations() {
3461        let source = br#"
3462            int FIRST = TRUE;
3463            int SECOND = FALSE;
3464            void main() {}
3465        "#;
3466        let mut source_map = SourceMap::new();
3467        let root_id = source_map.add_file("globals_debug.nss".to_string(), source.to_vec());
3468        let script = parse_text(
3469            root_id,
3470            std::str::from_utf8(source).expect("utf-8"),
3471            Some(&test_langspec()),
3472        )
3473        .expect("script should parse");
3474
3475        let artifacts = compile_script_with_source_map(
3476            &script,
3477            &source_map,
3478            root_id,
3479            Some(&test_langspec()),
3480            CompileOptions::default(),
3481        )
3482        .expect("compile should succeed");
3483        let ndb = read_ndb(&mut std::io::Cursor::new(
3484            artifacts.ndb.expect("NDB output should be present"),
3485        ))
3486        .expect("NDB should parse");
3487
3488        let first = ndb
3489            .variables
3490            .iter()
3491            .find(|variable| variable.label == "FIRST")
3492            .expect("FIRST global should be present");
3493        let second = ndb
3494            .variables
3495            .iter()
3496            .find(|variable| variable.label == "SECOND")
3497            .expect("SECOND global should be present");
3498
3499        assert_eq!(
3500            first.binary_start, 10,
3501            "first global should begin after loader + first RunstackAdd"
3502        );
3503        assert_eq!(
3504            second.binary_start, 12,
3505            "second global should begin after loader + second RunstackAdd"
3506        );
3507        assert_eq!(first.stack_loc, 0);
3508        assert_eq!(second.stack_loc, 4);
3509    }
3510
3511    #[test]
3512    fn compiles_switch_cases_backed_by_const_globals() -> Result<(), Box<dyn std::error::Error>> {
3513        let script = parse_text(
3514            SourceId::new(84),
3515            r#"
3516                const int CASE_A = 1 + 2;
3517                int StartingConditional() {
3518                    int nValue = 3;
3519                    switch (nValue) {
3520                        case CASE_A:
3521                            return TRUE;
3522                        default:
3523                            return FALSE;
3524                    }
3525                    return FALSE;
3526                }
3527            "#,
3528            Some(&test_langspec()),
3529        )?;
3530
3531        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3532        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3533
3534        assert!(
3535            instructions
3536                .iter()
3537                .any(|instruction| instruction.opcode == NcsOpcode::Equal),
3538            "switch codegen should materialize a case comparison",
3539        );
3540        assert!(
3541            instructions
3542                .iter()
3543                .any(|instruction| instruction.opcode == NcsOpcode::Jmp),
3544            "switch codegen should branch into case bodies",
3545        );
3546        Ok(())
3547    }
3548
3549    #[test]
3550    fn compiles_magic_literals_with_source_context() -> Result<(), Box<dyn std::error::Error>> {
3551        let source = br#"void main() {
3552    string sFunction = __FUNCTION__;
3553    string sFile = __FILE__;
3554    int nLine = __LINE__;
3555}
3556"#;
3557        let mut source_map = SourceMap::new();
3558        let root_id = source_map.add_file("magic_literals.nss".to_string(), source.to_vec());
3559        let script = parse_text(
3560            root_id,
3561            std::str::from_utf8(source).expect("utf-8"),
3562            Some(&test_langspec()),
3563        )?;
3564
3565        let artifacts = compile_script_with_source_map(
3566            &script,
3567            &source_map,
3568            root_id,
3569            Some(&test_langspec()),
3570            CompileOptions::default(),
3571        )?;
3572        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3573
3574        let string_constants = instructions
3575            .iter()
3576            .filter(|instruction| {
3577                instruction.opcode == NcsOpcode::Constant
3578                    && instruction.auxcode == crate::NcsAuxCode::TypeString
3579            })
3580            .map(|instruction| decode_string_constant(&instruction.extra))
3581            .collect::<Vec<_>>();
3582        let integer_constants = instructions
3583            .iter()
3584            .filter(|instruction| {
3585                instruction.opcode == NcsOpcode::Constant
3586                    && instruction.auxcode == crate::NcsAuxCode::TypeInteger
3587            })
3588            .map(|instruction| decode_integer_constant(&instruction.extra))
3589            .collect::<Vec<_>>();
3590
3591        assert!(string_constants.iter().any(|value| value == "main"));
3592        assert!(
3593            string_constants
3594                .iter()
3595                .any(|value| value == "magic_literals.nss")
3596        );
3597        assert!(integer_constants.contains(&4));
3598        Ok(())
3599    }
3600
3601    #[test]
3602    fn compiles_magic_literals_without_source_map() -> Result<(), Box<dyn std::error::Error>> {
3603        let script = parse_text(
3604            SourceId::new(86),
3605            r#"
3606                void main() {
3607                    string sFunction = __FUNCTION__;
3608                    string sFile = __FILE__;
3609                    int nLine = __LINE__;
3610                }
3611            "#,
3612            Some(&test_langspec()),
3613        )?;
3614
3615        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3616        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3617
3618        let string_constants = instructions
3619            .iter()
3620            .filter(|instruction| {
3621                instruction.opcode == NcsOpcode::Constant
3622                    && instruction.auxcode == crate::NcsAuxCode::TypeString
3623            })
3624            .map(|instruction| decode_string_constant(&instruction.extra))
3625            .collect::<Vec<_>>();
3626        let integer_constants = instructions
3627            .iter()
3628            .filter(|instruction| {
3629                instruction.opcode == NcsOpcode::Constant
3630                    && instruction.auxcode == crate::NcsAuxCode::TypeInteger
3631            })
3632            .map(|instruction| decode_integer_constant(&instruction.extra))
3633            .collect::<Vec<_>>();
3634
3635        assert!(string_constants.iter().any(|value| value == "main"));
3636        assert!(string_constants.iter().any(|value| value.is_empty()));
3637        assert!(integer_constants.contains(&0));
3638        Ok(())
3639    }
3640
3641    #[test]
3642    fn compiles_date_and_time_magic_literals() -> Result<(), Box<dyn std::error::Error>> {
3643        let script = parse_text(
3644            SourceId::new(87),
3645            r#"
3646                void main() {
3647                    string sDate = __DATE__;
3648                    string sTime = __TIME__;
3649                }
3650            "#,
3651            Some(&test_langspec()),
3652        )?;
3653
3654        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3655        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3656        let string_constants = instructions
3657            .iter()
3658            .filter(|instruction| {
3659                instruction.opcode == NcsOpcode::Constant
3660                    && instruction.auxcode == crate::NcsAuxCode::TypeString
3661            })
3662            .map(|instruction| decode_string_constant(&instruction.extra))
3663            .collect::<Vec<_>>();
3664
3665        assert!(
3666            string_constants.iter().any(|value| {
3667                let bytes = value.as_bytes();
3668                value.len() == 11
3669                    && bytes.get(3) == Some(&b' ')
3670                    && bytes.get(6) == Some(&b' ')
3671                    && value.chars().skip(7).all(|ch| ch.is_ascii_digit())
3672            }),
3673            "__DATE__ should compile into a macro-style date string",
3674        );
3675        assert!(
3676            string_constants.iter().any(|value| {
3677                let bytes = value.as_bytes();
3678                value.len() == 8
3679                    && bytes.get(2) == Some(&b':')
3680                    && bytes.get(5) == Some(&b':')
3681                    && value
3682                        .chars()
3683                        .enumerate()
3684                        .all(|(index, ch)| matches!(index, 2 | 5) || ch.is_ascii_digit())
3685            }),
3686            "__TIME__ should compile into a macro-style time string",
3687        );
3688        Ok(())
3689    }
3690
3691    #[test]
3692    fn compiles_conditional_expressions() -> Result<(), Box<dyn std::error::Error>> {
3693        let script = parse_text(
3694            SourceId::new(88),
3695            r#"
3696                int StartingConditional() {
3697                    int nCurHP = GetCurrentHitPoints();
3698                    return nCurHP > 0 ? TRUE : FALSE;
3699                }
3700            "#,
3701            Some(&test_langspec()),
3702        )?;
3703
3704        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3705        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3706
3707        assert!(
3708            instructions
3709                .iter()
3710                .any(|instruction| instruction.opcode == NcsOpcode::Jz),
3711            "conditional expression should branch on the computed condition",
3712        );
3713        assert!(
3714            instructions
3715                .iter()
3716                .any(|instruction| instruction.opcode == NcsOpcode::Jmp),
3717            "conditional expression should merge control flow after one arm executes",
3718        );
3719        Ok(())
3720    }
3721
3722    #[test]
3723    fn compiles_nested_field_assignments() -> Result<(), Box<dyn std::error::Error>> {
3724        let script = parse_text(
3725            SourceId::new(89),
3726            r#"
3727                struct Inner { int value; };
3728                struct Outer { struct Inner inner; };
3729                void main() {
3730                    struct Outer outer;
3731                    outer.inner.value = 1;
3732                }
3733            "#,
3734            Some(&test_langspec()),
3735        )?;
3736
3737        let artifacts = compile_script(&script, Some(&test_langspec()), CompileOptions::default())?;
3738        let instructions = decode_ncs_instructions(&artifacts.ncs)?;
3739
3740        assert!(
3741            instructions
3742                .iter()
3743                .any(|instruction| instruction.opcode == NcsOpcode::Assignment),
3744            "nested field assignment should lower to an assignment opcode",
3745        );
3746        Ok(())
3747    }
3748}