dynasm/
lib.rs

1#![cfg_attr(feature = "filelocal", feature(proc_macro_span))]
2//! The dynasm crate contains the procedural macros that power the magic of dynasm-rs. It seamlessly integrates
3//! a full dynamic assembler for several assembly dialects with Rust code.
4//! 
5//! As this is a proc-macro crate, it only exports the `dynasm!` and `dynasm_backwards!` macros.
6//! Any directives used in these macro invocations are normally local to the invocation itself, unless
7//! the `filelocal` crate feature is used. This feature requires a nightly compiler.
8//!
9//! Additionally, the `dynasm_opmap` and `dynasm_extract` development features can be used to export two additional macros
10//! with matching names. These macros expand into instruction listing overviews and are normally only used for documentation purposes.
11
12extern crate proc_macro;
13
14use syn::parse;
15use syn::{Token, parse_macro_input};
16use proc_macro2::{TokenTree, TokenStream};
17use quote::quote;
18use proc_macro_error2::proc_macro_error;
19
20use std::collections::HashMap;
21
22#[cfg(feature = "filelocal")]
23use std::sync::{MutexGuard, Mutex};
24#[cfg(feature = "filelocal")]
25use std::path::PathBuf;
26#[cfg(any(feature = "filelocal", feature = "dynasm_opmap", feature = "dynasm_extract"))]
27use proc_macro2::Span;
28
29/// Module with common infrastructure across assemblers
30mod common;
31/// Module with architecture-specific assembler implementations
32mod arch;
33/// Module contaning the implementation of directives
34mod directive;
35/// Module containing utility functions for creating TokenTrees from assembler / directive output
36mod serialize;
37/// Module containing utility functions for parsing
38mod parse_helpers;
39
40/// The whole point. This macro compiles given assembly/Rust templates down to `DynasmApi` and `DynasmLabelApi`
41/// compliant calls to an assembler.
42#[proc_macro]
43#[proc_macro_error]
44pub fn dynasm(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
45    // try parsing the tokenstream into a dynasm struct containing
46    // an abstract representation of the statements to create
47    let dynasm = parse_macro_input!(tokens as Dynasm);
48
49    // serialize the resulting output into tokens
50    serialize::serialize(&dynasm.target, dynasm.stmts).into()
51}
52
53/// Similar to `dynasm!`, but the calls to the assembler are executed in piecewise reversed order.
54/// This is to allow the system to be used with assemblers that assemble backwards.
55/// Currently this is not supported by the `dynasmrt` crate, but this allows experimentation with it
56/// out of tree.
57#[proc_macro]
58#[proc_macro_error]
59pub fn dynasm_backwards(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
60    // try parsing the tokenstream into a dynasm struct containing
61    // an abstract representation of the statements to create
62    let dynasm = parse_macro_input!(tokens as Dynasm);
63
64    // reverse the statement stream
65    let stmts = serialize::invert(dynasm.stmts);
66
67    // serialize the resulting output into tokens
68    serialize::serialize(&dynasm.target, stmts).into()
69}
70
71/// output from parsing a full dynasm invocation. target represents the first dynasm argument, being the assembler
72/// variable being used. stmts contains an abstract representation of the statements to be generated from this dynasm
73/// invocation.
74struct Dynasm {
75    target: TokenTree,
76    stmts: Vec<common::Stmt>
77}
78
79/// top-level parsing. Handles common prefix symbols and diverts to the selected architecture
80/// when an assembly instruction is encountered. When parsing fails an Err() is returned, when
81/// non-parsing errors happen err() will be called, but this function returns Ok().
82impl parse::Parse for Dynasm {
83    fn parse(input: parse::ParseStream) -> parse::Result<Self> {
84
85        // parse the assembler target declaration
86        let target: syn::Expr = input.parse()?;
87        // and just convert it back to a tokentree since that's how we'll always be using it.
88        let target = common::delimited(target);
89
90        // get file-local data (alias definitions, current architecture)
91        let mut provider = ContextProvider::new();
92        let invocation_context = provider.get_context_mut();
93
94        // prepare the statement buffer
95        let mut stmts = Vec::new();
96
97        // if we're not at the end of the macro, we should be expecting a semicolon and a new directive/statement/label/op
98        while !input.is_empty() {
99            let _: Token![;] = input.parse()?;
100
101            // ;; stmt
102            if input.peek(Token![;]) {
103                let _: Token![;] = input.parse()?;
104
105                // collect all tokentrees till the next ;
106                let mut buffer = TokenStream::new();
107                while !(input.is_empty() || input.peek(Token![;])) {
108                    buffer.extend(std::iter::once(input.parse::<TokenTree>()?));
109                }
110                // glue an extra ; on there
111                buffer.extend(quote! { ; } );
112
113                if !buffer.is_empty() {
114                    // ensure that the statement is actually a proper statement and then emit it for serialization
115                    let stmt: syn::Stmt = syn::parse2(buffer)?;
116                    stmts.push(common::Stmt::Stmt(quote!{ #stmt }));
117                }
118                continue;
119            }
120
121            // ; -> label :
122            if input.peek(Token![->]) {
123                let _: Token![->] = input.parse()?;
124
125                let name: syn::Ident = input.parse()?;
126                let _: Token![:] = input.parse()?;
127
128                stmts.push(common::Stmt::GlobalLabel(name));
129                continue;
130            }
131
132            // ; => expr
133            if input.peek(Token![=>]) {
134                let _: Token![=>] = input.parse()?;
135
136                let expr: syn::Expr = input.parse()?;
137
138                stmts.push(common::Stmt::DynamicLabel(common::delimited(expr)));
139                continue;
140            }
141
142            // ; label :
143            if input.peek(syn::Ident) && input.peek2(Token![:]) {
144
145                let name: syn::Ident = input.parse()?;
146                let _: Token![:] = input.parse()?;
147
148                stmts.push(common::Stmt::LocalLabel(name));
149                continue;
150            }
151
152
153            // ; . directive
154            if input.peek(Token![.]) {
155                let _: Token![.] = input.parse()?;
156
157                directive::evaluate_directive(invocation_context, &mut stmts, input)?;
158            } else {
159                // anything else is an assembly instruction which should be in current_arch
160
161                let mut state = State {
162                    stmts: &mut stmts,
163                    invocation_context: &*invocation_context,
164                };
165                invocation_context.current_arch.compile_instruction(&mut state, input)?;
166            }
167
168        }
169
170        Ok(Dynasm {
171            target,
172            stmts
173        })
174    }
175}
176
177/// This is only compiled when the dynasm_opmap feature is used. It exports the internal assembly listings
178/// into a string that can then be included into the documentation for dynasm.
179#[cfg(feature = "dynasm_opmap")]
180#[proc_macro]
181pub fn dynasm_opmap(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
182
183    // parse to ensure that no macro arguments were provided
184    let opmap = parse_macro_input!(tokens as DynasmOpmap);
185
186    let mut s = String::new();
187    s.push_str("% Instruction Reference\n\n");
188
189    s.push_str(&match opmap.arch.as_str() {
190        "x64" | "x86" => arch::x64::create_opmap(),
191        "aarch64" => arch::aarch64::create_opmap(),
192        "riscv" => arch::riscv::create_opmap(),
193        x => panic!("Unknown architecture {}", x)
194    });
195
196    let token = quote::quote_spanned! { Span::mixed_site()=>
197        #s
198    };
199    token.into()
200}
201
202/// This is only compiled when the dynasm_extract feature is used. It exports the internal assembly listings
203/// into a string that can then be included into the documentation for dynasm.
204#[cfg(feature = "dynasm_extract")]
205#[proc_macro]
206pub fn dynasm_extract(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
207
208    // parse to ensure that no macro arguments were provided
209    let opmap = parse_macro_input!(tokens as DynasmOpmap);
210
211    let s = match opmap.arch.as_str() {
212        "x64" | "x86" => "UNIMPLEMENTED".into(),
213        "aarch64" => arch::aarch64::extract_opmap(),
214        "riscv" => arch::riscv::extract_opmap(),
215        x => panic!("Unknown architecture {}", x)
216    };
217
218    let token = quote::quote_spanned! { Span::mixed_site()=>
219        #s
220    };
221    token.into()
222}
223
224/// As dynasm_opmap takes no args it doesn't parse to anything
225
226#[cfg(any(feature="dynasm_extract", feature="dynasm_opmap"))]
227struct DynasmOpmap {
228    pub arch: String
229}
230
231/// As dynasm_opmap takes no args it doesn't parse to anything.
232/// This just exists so syn will give an error when no args are present.
233#[cfg(any(feature="dynasm_extract", feature="dynasm_opmap"))]
234impl parse::Parse for DynasmOpmap {
235    fn parse(input: parse::ParseStream) -> parse::Result<Self> {
236        let arch: syn::Ident = input.parse()?;
237
238        Ok(DynasmOpmap {
239            arch: arch.to_string()
240        })
241    }
242}
243
244/// This struct contains all non-parsing state that dynasm! requires while parsing and compiling
245struct State<'a> {
246    pub stmts: &'a mut Vec<common::Stmt>,
247    pub invocation_context: &'a DynasmContext,
248}
249
250// File local data implementation.
251
252// context inside of a dynasm invocation, manipulated by directives and such
253struct DynasmContext {
254    pub current_arch: Box<dyn arch::Arch>,
255    pub aliases: HashMap<String, String>,
256}
257
258impl DynasmContext {
259    fn new() -> DynasmContext {
260        DynasmContext {
261            current_arch: arch::from_str(arch::CURRENT_ARCH).expect("Invalid default architecture"),
262            aliases: HashMap::new()
263        }
264    }
265}
266
267// Oneshot context provider
268#[cfg(not(feature = "filelocal"))]
269struct ContextProvider {
270    context: DynasmContext
271}
272
273#[cfg(not(feature = "filelocal"))]
274impl ContextProvider {
275    pub fn new() -> ContextProvider {
276        ContextProvider {
277            context: DynasmContext::new()
278        }
279    }
280
281    pub fn get_context_mut(&mut self) -> &mut DynasmContext {
282        &mut self.context
283    }
284}
285
286/// Filelocal context provider
287#[cfg(feature = "filelocal")]
288struct ContextProvider {
289    guard: MutexGuard<'static, HashMap<PathBuf, DynasmContext>>
290}
291
292#[cfg(feature = "filelocal")]
293impl ContextProvider {
294    pub fn new() -> ContextProvider {
295        ContextProvider {
296            guard: CONTEXT_STORAGE.lock().unwrap()
297        }
298    }
299
300    pub fn get_context_mut(&mut self) -> &mut DynasmContext {
301        // get the file that generated this macro expansion
302        let span = Span::call_site().unstable();
303
304        // and use the file that that was at as scope for resolving dynasm data
305        let id = span.source_file().path();
306
307        self.guard.entry(id).or_insert_with(DynasmContext::new)
308    }
309}
310
311#[cfg(feature = "filelocal")]
312lazy_static::lazy_static! {
313    static ref CONTEXT_STORAGE: Mutex<HashMap<PathBuf, DynasmContext>> = Mutex::new(HashMap::new());
314}