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}