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#[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 #[default]
29 O0,
30 O1,
32 O2,
34 O3,
36}
37
38#[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 pub semantic: SemanticOptions,
43 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#[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 pub ncs: Vec<u8>,
61 pub ndb: Option<Vec<u8>>,
63}
64
65#[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 pub span: Option<crate::Span>,
70 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#[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(crate::SemanticError),
122 Hir(crate::HirLowerError),
124 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
158pub 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
191pub 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
206pub 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
285pub 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(¶meter.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(¶meter.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(¶meter.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(¶meter.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 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(¶meter.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}