opcode_macros/
lib.rs

1//! Provides the [opcode_match!] macro.
2
3#![forbid(missing_docs)]
4
5mod parser;
6mod generator;
7mod block;
8mod opcode_gen;
9
10use opcode_gen::OpcodeGen;
11use parser::OpcodeMatches;
12use proc_macro::TokenStream;
13use syn::parse_macro_input;
14
15use crate::generator::generate;
16
17/// Generates a complex match statement
18///
19/// # Format
20///
21/// The basic format is like this:
22///
23/// ```rust
24/// # use opcode_macros::opcode_match;
25/// # let opcode = 0u8;
26/// # mod namespace {
27/// #     pub const A_1: u8 = 0;
28/// #     pub const A_2: u8 = 0;
29/// #     pub const B_1: u8 = 0;
30/// #     pub const B_2: u8 = 0;
31/// # }
32/// # let result =
33/// opcode_match! {
34///     opcode as u8 in namespace,
35///     [[A_1: a1, A_2: a2], [B_1: b1, B_2: b2]] => {
36///         // Code
37///         # 1
38///     }
39///     _ => {
40///         // Code
41///         # 0
42///     }
43/// }
44/// # ;
45/// # assert_eq!(result, 1);
46/// ```
47///
48/// It generates something like this:
49///
50/// ```rust
51/// # let opcode = 0u8;
52/// # mod namespace {
53/// #     pub const A_1: u8 = 0;
54/// #     pub const A_2: u8 = 0;
55/// #     pub const B_1: u8 = 0;
56/// #     pub const B_2: u8 = 0;
57/// # }
58/// const A_1_B_1: u8 = namespace::A_1 | namespace::B_1;
59/// const A_1_B_2: u8 = namespace::A_1 | namespace::B_2;
60/// const A_2_B_1: u8 = namespace::A_2 | namespace::B_1;
61/// const A_2_B_2: u8 = namespace::A_2 | namespace::B_2;
62/// match opcode {
63///     A_1_B_1 => { /* Code */ }
64///     A_1_B_2 => { /* Code */ }
65///     A_2_B_1 => { /* Code */ }
66///     A_2_B_2 => { /* Code */ }
67///     _ => { /* Code */ }
68/// }
69/// ```
70///
71/// ## Match Arm Headers
72///
73/// Match arm headers is something like `[[A_1: a1, A_2: a2], [B_1: b1, B_2: b2]]`.
74///
75/// For example, in eBPF opcodes, `BPF_ALU | BPF_K | BPF_ADD` is an opcode for
76/// 32-bit addition with constants, while `BPF_ALU | BPF_K | BPF_SUB` is an opcode
77/// for 32-bit subtraction with constants. To match against these opcodes,
78/// we use the following code:
79///
80/// ```rust
81/// # use opcode_macros::opcode_match;
82/// # use ebpf_consts::*;
83/// # let opcode = 0x04u8;
84/// # let mut result = 0u64;
85/// # let dst = 10u64;
86/// # let imm = 10u64;
87/// opcode_match! {
88///     opcode as u8 in ebpf_consts,
89///     [[BPF_ALU: _], [BPF_K: _],
90///      [BPF_ADD: add, BPF_SUB: sub]] => {
91///         result = dst.#"wrapping_{}"2(imm);
92///     }
93///     _ => {}
94/// }
95/// # assert_eq!(result, 20);
96/// ```
97///
98/// We will talk about the templating rules later.
99///
100/// In the example above, you can also use some other variants:
101/// - `[BPF_ADD: "add", BPF_SUB: "sub"]`
102/// - `[BPF_ADD: [add], BPF_SUB: [sub]]`
103/// - `[BPF_ADD: ["add"], BPF_SUB: ["sub"]]`
104/// - `[BPF_ADD: ["add", "extra1"], BPF_SUB: ["sub", "extra2"]]`
105///
106/// If you want to substitutes parts of the code with symbols like "+",
107/// you will need to quote the symbols like `[BPF_ADD: "+"]`.
108///
109/// ## Code Template
110///
111/// ### Substitution
112///
113/// This is not a real life example.
114///
115/// ```rust
116/// # use opcode_macros::opcode_match;
117/// # use ebpf_consts::*;
118/// # use core::ops::Add;
119/// # let opcode = 0u8;
120/// opcode_match! {
121///     opcode as u8 in ebpf_consts,
122///     [
123///         // Group 0
124///         [BPF_K:   ["const", 1, 2, "Constant Operation"]],
125///         // Group 1
126///         [BPF_ADD: ["add", 3, 4, "Addition Operation"]],
127///     ] => {
128///         // Use the first token in group 0 as a string
129///         assert_eq!(#0, "const");
130///         // Use the fourth token in group 0 as a string
131///         assert_eq!(#:3:0, "Constant Operation");
132///         assert_eq!(#:0:1, "add");
133///         assert_eq!(#:3:1, "Addition Operation");
134///
135///         // Use raw tokens
136///         assert_eq!(#:1:1, "3");
137///         assert_eq!(#:1:=1, 3);
138///         // 30.add(40) == 70, where #=1 is just #:0:=1
139///         let value = 30isize;
140///         assert_eq!(value.#=1(40), 70);
141///
142///         // With in-token substitution: add -> wrapping_add
143///         assert_eq!(value.#"wrapping_{}"1(40), 70);
144///     }
145///     _ => panic!(),
146/// }
147/// ```
148///
149/// ### Conditional Blocks
150///
151/// ```rust
152/// use opcode_macros::opcode_match;
153/// use ebpf_consts::*;
154///
155/// # let opcode = BPF_X | BPF_ALU;
156/// opcode_match! {
157///     opcode as u8 in ebpf_consts,
158///     [[BPF_X: x, BPF_K: k], [BPF_ALU: "alu", BPF_ALU64: "alu64"]] => {
159///         #?((x))
160///             println!("In V1 branch");
161///             assert_eq!(#0, "x");                ##
162///
163///         #?((k))
164///             println!("In V2 && {} branch", #1);
165///             assert_eq!(#0, "k");                ##
166///
167///         #?((!"k"))
168///             println!("In V2 && {} branch", #1);
169///             assert_eq!(#0, "x");                ##
170///         println!("Common");
171///     }
172///     _ => panic!(),
173/// };
174/// ```
175///
176/// The grammar is `#?((cond1, cond2)|(cond3|cond4)|...) CODE ##`,
177/// making the code only injected if the opcode matches
178/// `(cond1 && cond2) || (cond3 && cond4) || ...`.
179/// A condition can be negated with an exclamation mark `!cond1`.
180///
181/// We don't allow nested conditions.
182#[proc_macro]
183pub fn opcode_match(input: TokenStream) -> TokenStream {
184    let matches = parse_macro_input!(input as OpcodeMatches);
185    generate(&matches)
186}
187
188/// Generates opcode from bit field enums
189///
190/// # Example
191///
192/// ```rust
193/// use opcode_macros::opcode_gen;
194/// opcode_gen! {
195///     for BPF_* in ebpf_consts as u8 {
196///         [
197///             [BPF_ALU, BPF_ALU64],
198///             [BPF_K, BPF_X],
199///             [BPF_ADD, BPF_SUB],
200///         ]
201///         [
202///             [BPF_JMP, BPF_JMP32],
203///             [BPF_K, BPF_X],
204///             [BPF_JLE, BPF_JLT],
205///         ]
206///     }
207/// }
208/// # use ebpf_consts::*;
209/// assert_eq!(BPF_ALU64_K_ADD, BPF_ALU64 | BPF_K | BPF_ADD);
210/// assert_eq!(BPF_JMP_X_JLT, BPF_JMP | BPF_X | BPF_JLT);
211/// ```
212#[proc_macro]
213pub fn opcode_gen(input: TokenStream) -> TokenStream {
214    let gen = parse_macro_input!(input as OpcodeGen);
215    gen.generate()
216}