1#![recursion_limit = "256"]
33
34extern crate proc_macro;
35
36use heck::ShoutySnakeCase;
37use heck::SnakeCase;
38use proc_macro::TokenStream;
39use quote::quote;
40use syn::parse_macro_input;
41use syn::spanned::Spanned;
42use syn::AttributeArgs;
43use syn::Ident;
44use syn::ItemStruct;
45use syn::Lit;
46use syn::LitInt;
47use syn::LitStr;
48use syn::NestedMeta;
49
50struct VMNameSet {
51 type_name: String,
52 name_allcaps: String,
53 name_lowercase: String,
54}
55
56struct VMMetaData {
57 capabilities: u32,
58 name_stylized: String,
60 custom_version: String,
61}
62
63#[allow(dead_code)]
64impl VMNameSet {
65 fn new(ident: String) -> Self {
66 let caps = ident.to_shouty_snake_case();
67 let lowercase = ident
68 .to_snake_case()
69 .chars()
70 .filter(|c| *c != '_')
71 .collect();
72 VMNameSet {
73 type_name: ident,
74 name_allcaps: caps,
75 name_lowercase: lowercase,
76 }
77 }
78
79 fn get_type_name(&self) -> &String {
81 &self.type_name
82 }
83
84 fn get_name_caps(&self) -> &String {
86 &self.name_allcaps
87 }
88
89 fn get_name_lowercase(&self) -> &String {
92 &self.name_lowercase
93 }
94
95 fn get_type_as_ident(&self) -> Ident {
97 Ident::new(&self.type_name, self.type_name.span())
98 }
99
100 fn get_lowercase_as_ident_append(&self, suffix: &str) -> Ident {
102 let concat = format!("{}{}", &self.name_lowercase, suffix);
103 Ident::new(&concat, self.name_lowercase.span())
104 }
105
106 fn get_lowercase_as_ident_prepend(&self, prefix: &str) -> Ident {
108 let concat = format!("{}{}", prefix, &self.name_lowercase);
109 Ident::new(&concat, self.name_lowercase.span())
110 }
111
112 fn get_caps_as_ident_append(&self, suffix: &str) -> Ident {
114 let concat = format!("{}{}", &self.name_allcaps, suffix);
115 Ident::new(&concat, self.name_allcaps.span())
116 }
117}
118
119impl VMMetaData {
120 fn new(args: AttributeArgs) -> Self {
121 assert_eq!(args.len(), 3, "Incorrect number of arguments supplied");
122
123 let vm_name_meta = &args[0];
124 let vm_capabilities_meta = &args[1];
125 let vm_version_meta = &args[2];
126
127 let vm_name_string = match vm_name_meta {
128 NestedMeta::Lit(lit) => {
129 if let Lit::Str(s) = lit {
130 let mut ret = s.value().to_string();
133 ret.push('\0');
134 ret
135 } else {
136 panic!("Literal argument type mismatch")
137 }
138 }
139 _ => panic!("Argument 1 must be a string literal"),
140 };
141
142 let vm_capabilities_string = match vm_capabilities_meta {
143 NestedMeta::Lit(lit) => {
144 if let Lit::Str(s) = lit {
145 s.value().to_string()
146 } else {
147 panic!("Literal argument type mismatch")
148 }
149 }
150 _ => panic!("Argument 2 must be a string literal"),
151 };
152
153 let capabilities_list_pruned: String = vm_capabilities_string
156 .chars()
157 .filter(|c| *c != '_' && *c != ' ')
158 .collect();
159 let capabilities_flags = {
160 let mut ret: u32 = 0;
161 for capability in capabilities_list_pruned.split(",") {
162 match capability {
163 "evm" => ret |= 1,
164 "ewasm" => ret |= 1 << 1,
165 "precompiles" => ret |= 1 << 2,
166 _ => panic!("Invalid capability specified."),
167 }
168 }
169 ret
170 };
171
172 let vm_version_string: String = if let NestedMeta::Lit(lit) = vm_version_meta {
173 match lit {
174 Lit::Str(s) => {
177 let mut ret = s.value().to_string();
178 ret.push('\0');
179 ret
180 }
181 _ => panic!("Literal argument type mismatch"),
182 }
183 } else {
184 panic!("Argument 3 must be a string literal")
185 };
186
187 assert_eq!(vm_name_string.matches('\0').count(), 1);
189 assert_eq!(vm_version_string.matches('\0').count(), 1);
190
191 VMMetaData {
192 capabilities: capabilities_flags,
193 name_stylized: vm_name_string,
194 custom_version: vm_version_string,
195 }
196 }
197
198 fn get_capabilities(&self) -> u32 {
199 self.capabilities
200 }
201
202 fn get_name_stylized_nulterm(&self) -> &String {
203 &self.name_stylized
204 }
205
206 fn get_custom_version_nulterm(&self) -> &String {
207 &self.custom_version
208 }
209}
210
211#[proc_macro_attribute]
212pub fn evmc_declare_vm(args: TokenStream, item: TokenStream) -> TokenStream {
213 let input: ItemStruct = parse_macro_input!(item as ItemStruct);
216
217 let vm_type_name: String = input.ident.to_string();
219
220 let names = VMNameSet::new(vm_type_name);
222
223 let meta_args = parse_macro_input!(args as AttributeArgs);
225 let vm_data = VMMetaData::new(meta_args);
226
227 let static_data_tokens = build_static_data(&names, &vm_data);
229 let capabilities_tokens = build_capabilities_fn(vm_data.get_capabilities());
230 let create_tokens = build_create_fn(&names);
231 let destroy_tokens = build_destroy_fn(&names);
232 let execute_tokens = build_execute_fn(&names);
233
234 let quoted = quote! {
235 #input
236 #static_data_tokens
237 #capabilities_tokens
238 #create_tokens
239 #destroy_tokens
240 #execute_tokens
241 };
242
243 quoted.into()
244}
245
246fn build_static_data(names: &VMNameSet, metadata: &VMMetaData) -> proc_macro2::TokenStream {
248 let static_name_ident = names.get_caps_as_ident_append("_NAME");
250 let static_version_ident = names.get_caps_as_ident_append("_VERSION");
251
252 let stylized_name_literal = LitStr::new(
254 metadata.get_name_stylized_nulterm().as_str(),
255 metadata.get_name_stylized_nulterm().as_str().span(),
256 );
257
258 let version_string = metadata.get_custom_version_nulterm();
260 let version_literal = LitStr::new(version_string.as_str(), version_string.as_str().span());
261
262 quote! {
263 static #static_name_ident: &'static str = #stylized_name_literal;
264 static #static_version_ident: &'static str = #version_literal;
265 }
266}
267
268fn build_capabilities_fn(capabilities: u32) -> proc_macro2::TokenStream {
270 let capabilities_string = capabilities.to_string();
271 let capabilities_literal = LitInt::new(&capabilities_string, capabilities.span());
272
273 quote! {
274 extern "C" fn __evmc_get_capabilities(instance: *mut ::evmc_vm::ffi::evmc_vm) -> ::evmc_vm::ffi::evmc_capabilities_flagset {
275 #capabilities_literal
276 }
277 }
278}
279
280fn build_create_fn(names: &VMNameSet) -> proc_macro2::TokenStream {
282 let type_ident = names.get_type_as_ident();
283 let fn_ident = names.get_lowercase_as_ident_prepend("evmc_create_");
284
285 let static_version_ident = names.get_caps_as_ident_append("_VERSION");
286 let static_name_ident = names.get_caps_as_ident_append("_NAME");
287
288 quote! {
290 #[no_mangle]
291 extern "C" fn #fn_ident() -> *const ::evmc_vm::ffi::evmc_vm {
292 let new_instance = ::evmc_vm::ffi::evmc_vm {
293 abi_version: ::evmc_vm::ffi::EVMC_ABI_VERSION as i32,
294 destroy: Some(__evmc_destroy),
295 execute: Some(__evmc_execute),
296 get_capabilities: Some(__evmc_get_capabilities),
297 set_option: None,
298 name: unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(#static_name_ident.as_bytes()).as_ptr() as *const i8 },
299 version: unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(#static_version_ident.as_bytes()).as_ptr() as *const i8 },
300 };
301
302 let container = ::evmc_vm::EvmcContainer::<#type_ident>::new(new_instance);
303
304 unsafe {
305 ::evmc_vm::EvmcContainer::into_ffi_pointer(container)
307 }
308 }
309 }
310}
311
312fn build_destroy_fn(names: &VMNameSet) -> proc_macro2::TokenStream {
314 let type_ident = names.get_type_as_ident();
315
316 quote! {
317 extern "C" fn __evmc_destroy(instance: *mut ::evmc_vm::ffi::evmc_vm) {
318 if instance.is_null() {
319 std::process::abort();
321 }
322 unsafe {
323 ::evmc_vm::EvmcContainer::<#type_ident>::from_ffi_pointer(instance);
325 }
326 }
327 }
328}
329
330fn build_execute_fn(names: &VMNameSet) -> proc_macro2::TokenStream {
332 let type_name_ident = names.get_type_as_ident();
333
334 quote! {
335 extern "C" fn __evmc_execute(
336 instance: *mut ::evmc_vm::ffi::evmc_vm,
337 host: *const ::evmc_vm::ffi::evmc_host_interface,
338 context: *mut ::evmc_vm::ffi::evmc_host_context,
339 revision: ::evmc_vm::ffi::evmc_revision,
340 msg: *const ::evmc_vm::ffi::evmc_message,
341 code: *const u8,
342 code_size: usize
343 ) -> ::evmc_vm::ffi::evmc_result
344 {
345 use evmc_vm::EvmcVm;
346
347 if instance.is_null() || msg.is_null() || (code.is_null() && code_size != 0) {
349 std::process::abort();
351 }
352
353 assert!(!instance.is_null());
354 assert!(!msg.is_null());
355
356 let execution_message: ::evmc_vm::ExecutionMessage = unsafe {
357 msg.as_ref().expect("EVMC message is null").into()
358 };
359
360 let empty_code = [0u8;0];
361 let code_ref: &[u8] = if code.is_null() {
362 assert_eq!(code_size, 0);
363 &empty_code
364 } else {
365 unsafe {
366 ::std::slice::from_raw_parts(code, code_size)
367 }
368 };
369
370 let container = unsafe {
371 ::evmc_vm::EvmcContainer::<#type_name_ident>::from_ffi_pointer(instance)
373 };
374
375 let result = ::std::panic::catch_unwind(|| {
376 if host.is_null() {
377 container.execute(revision, code_ref, &execution_message, None)
378 } else {
379 let mut execution_context = unsafe {
380 ::evmc_vm::ExecutionContext::new(
381 host.as_ref().expect("EVMC host is null"),
382 context,
383 )
384 };
385 container.execute(revision, code_ref, &execution_message, Some(&mut execution_context))
386 }
387 });
388
389 let result = if result.is_err() {
390 ::evmc_vm::ExecutionResult::new(::evmc_vm::ffi::evmc_status_code::EVMC_INTERNAL_ERROR, 0, None)
392 } else {
393 result.unwrap()
394 };
395
396 unsafe {
397 ::evmc_vm::EvmcContainer::into_ffi_pointer(container);
399 }
400
401 result.into()
402 }
403 }
404}