1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

use std::time::{SystemTime, UNIX_EPOCH};

use chrono::Local;
use darling::{ast::NestedMeta, util::Flag, Error, FromMeta};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use simics_api_sys::{SIM_VERSION, SIM_VERSION_COMPAT};
use syn::{parse_macro_input, ItemFn, ReturnType, Type};

#[derive(Debug, FromMeta)]
pub struct SimicsInitOpts {
    #[darling(default)]
    /// The name of the module, used in the `MOD:NAME` expression
    pub name: String,
    #[darling(multiple)]
    /// The list of classes to register. Classes not listed here will not be declared.
    pub class: Vec<String>,
    no_panic_hook: Flag,
}

pub trait IsResultType {
    fn is_result_type(&self) -> bool;
}

#[cfg(unix)]
const HOST_TYPE: &str = "linux64";

#[cfg(windows)]
const HOST_TYPE: &str = "win64";

impl IsResultType for ReturnType {
    fn is_result_type(&self) -> bool {
        match self {
            ReturnType::Default => false,
            ReturnType::Type(_, ty) => match &**ty {
                Type::Path(p) => p
                    .path
                    .segments
                    .last()
                    .map(|l| l.ident == "Result")
                    .unwrap_or(false),
                _ => false,
            },
        }
    }
}

pub fn generate_exports(opts: &SimicsInitOpts) -> TokenStream2 {
    let name = &opts.name;
    let classes = &opts.class;
    let epoch_time = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("System time before epoch")
        .as_secs();
    let api = SIM_VERSION.to_string().chars().take(1).collect::<String>();
    let classes = classes
        .iter()
        .map(|c| format!("CLS:{}", c))
        .collect::<Vec<_>>();
    let classes = classes.join(";");

    let capabilities = vec![
        format!("VER:{SIM_VERSION_COMPAT}"),
        format!("ABI:{SIM_VERSION}"),
        format!("API:{api}"),
        "BLD:0".to_string(),
        "BLD_NS:__simics_project__".to_string(),
        format!("BUILDDATE:{epoch_time}"),
        format!("MOD:{name}"),
        classes,
        format!("HOSTTYPE:{HOST_TYPE}"),
        "THREADSAFE".to_string(),
        " ".repeat(43),
    ];
    let module_capabilities_string = capabilities.join(";") + ";" + "\x00";
    let module_capabilities_bytes = module_capabilities_string.as_bytes().to_vec();
    let module_capabilities_len = module_capabilities_bytes.len();

    // Get the date in the format like "Thu Jan 18 15:37:54 2024"
    let module_date_string = Local::now().format("%a %b %d %T %Y\x00").to_string();
    let module_date_bytes = module_date_string.as_bytes().to_vec();
    let module_date_len = module_date_bytes.len();

    quote! {
        #[no_mangle]
        /// The module capabilities list
        pub static _module_capabilities_: [u8; #module_capabilities_len] = [#(#module_capabilities_bytes),*];
        #[no_mangle]
        /// The module build date
        pub static _module_date: [u8; #module_date_len] = [#(#module_date_bytes),*];
    }
}

pub fn simics_init_impl(args: TokenStream, input: TokenStream) -> TokenStream {
    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
        Ok(a) => a,
        Err(e) => return TokenStream::from(Error::from(e).write_errors()),
    };

    let input = parse_macro_input!(input as ItemFn);

    let opts = match SimicsInitOpts::from_list(&attr_args) {
        Ok(a) => a,
        Err(e) => return TokenStream::from(e.write_errors()),
    };

    // Get the original ident and visibility before we change them
    let inner_ident = &input.sig.ident;

    let maybe_expect = &input
        .sig
        .output
        .is_result_type()
        .then_some(quote!(.expect("Failed while executing init")))
        .unwrap_or(quote!());

    let maybe_ty_generics = (!&input.sig.generics.params.is_empty()).then_some({
        let params = &input.sig.generics.params;
        quote!(::<#params>)
    });

    let Some(args) = &input
        .sig
        .inputs
        .iter()
        .map(|i| match i {
            syn::FnArg::Receiver(_) => None,
            syn::FnArg::Typed(t) => {
                let pat = &t.pat;
                Some(quote!(#pat))
            }
        })
        .collect::<Option<Vec<_>>>()
    else {
        return Error::custom("Methods with a receiver are not supported")
            .write_errors()
            .into();
    };

    let exports = generate_exports(&opts);

    let maybe_set_panic_hook = if !opts.no_panic_hook.is_present() {
        quote! {
            std::panic::set_hook(Box::new(|panic_info| {
                simics::panic_handler(panic_info)
            }));
        }
    } else {
        quote!()
    };

    let wrapper = quote! {
        #[no_mangle]
        /// Exported symbol called by simics when module is loaded
        pub extern "C" fn _simics_module_init() {
            #maybe_set_panic_hook
            #inner_ident #maybe_ty_generics(#(#args),*) #maybe_expect;
        }
    };

    quote! {
        #input
        #wrapper
        #exports
    }
    .into()
}