directcpp_macro/
lib.rs

1mod code_match;
2mod mangle;
3mod parse;
4mod util;
5mod tests;
6
7use parse::{Functions, map_to_cxx};
8use std::collections::{HashMap, HashSet};
9use proc_macro2::{TokenStream, Span};
10use proc_macro::{TokenStream as TS0, TokenTree};
11use std::env;
12use regex::Regex;
13use syn;
14use std::str::FromStr;
15use std::sync::Mutex;
16use crate::mangle::*;
17use crate::util::*;
18
19
20const TYPE_POD:i32 = 0;
21const TYPE_DTOR_TRIVIAL_MOVE:i32 = 1;  // 假定所有类型默认都是trivial move, non-trivial dtor
22
23lazy_static::lazy_static! {
24	static ref TYPE_STRATEGY: Mutex<HashMap<String, i32>> = Mutex::new(HashMap::new());
25}
26
27#[derive(Default)]
28#[allow(dead_code)]
29pub(crate) struct FFIBuilder{
30	is_cpp: bool,
31	extc_code: String,
32	norm_code: String,
33	err_str: String,
34	asm_used: bool
35}
36
37impl FFIBuilder {
38	pub fn new(reset:bool) -> Self {
39		if reset {
40			TYPE_STRATEGY.lock().unwrap().clear();
41		}
42		Self::default()
43	}
44
45	fn dtor_code(tp: &str) -> String {
46		let tp = map_to_cxx(tp);
47		let dtor_name = dtor_name(tp);
48		let tp1 = tp.replace("<", "_").replace(">", "_");
49		format!("\t#[link_name = \"{dtor_name}\"]\n\tfn ffi__free_{tp1}(__o: *mut usize);\n")
50	}
51	fn sp_dtor_code(tp: &str) -> String {
52		let dtor_name = sp_dtor_name(tp);
53		format!("\t#[link_name = \"{dtor_name}\"]\n\tfn ffi__freeSP_{tp}(__o: *mut usize);\n")
54	}
55
56	fn show_dtor(self: &mut Self, tp: &str, rtwrap:&str, tp_cpp: &str)->Result<(), &str> {
57		let tp_strategy = match rtwrap {
58			"POD" => TYPE_POD,
59			// type can be dtor, and can be trivial move. trivial move is required for Rust.
60			"SharedPtr"|"UniquePtr"|"Vec"|"" => TYPE_DTOR_TRIVIAL_MOVE,
61			_ => {
62				self.err_str = format!("type {} not supported", rtwrap);
63				return Err(&self.err_str);
64			}
65		};
66		let mut mp = TYPE_STRATEGY.lock().unwrap();
67		let mut tp1 = if let Some(x) = mp.get(tp_cpp) {
68			if *x >> 16 != tp_strategy {
69				self.err_str = format!("type {tp} strategy conflict");
70				return Err(&self.err_str);
71			}
72			*x
73		} else {
74			tp_strategy << 16
75		};
76
77		if tp_strategy != TYPE_POD {
78			if (tp1 & 1) == 0 && rtwrap != "SharedPtr" {
79				tp1 |= 1;
80				self.extc_code += &Self::dtor_code(tp_cpp);
81			}
82			if rtwrap == "UniquePtr" && tp1 & 2 == 0 {
83				tp1 |= 2;
84				self.norm_code += &format!("
85impl ManDtor for {tp} {{
86	unsafe fn __dtor(ptr: *mut [u8;0]) {{
87		if ptr as usize != 0 {{
88			ffi__free_{tp}(ptr as *mut usize);
89		}}
90	}}
91}}");
92			}
93			if rtwrap == "SharedPtr" && tp1 & 4 == 0 {
94				tp1 |= 4;
95				self.extc_code += &Self::sp_dtor_code(tp);
96				self.norm_code += &format!("
97impl DropSP for {tp} {{
98	unsafe fn __drop_sp(ptr: *mut [u8;0]) {{
99		if ptr as usize != 0 {{
100			ffi__freeSP_{tp}(ptr as *mut usize);
101		}}
102	}}
103}}\n");
104			}
105		}
106		mp.insert(tp_cpp.to_string(), tp1);
107		Ok(())
108	}
109
110	fn get_link_name(self: &Self, func: &SimpFunc, is_cpp: bool)
111		-> Result<String, &'static str>
112	{
113		if ! is_cpp {
114			Ok(func.fn_name.to_string())
115		} else {
116			mangle(&func)
117		}
118	}
119
120	fn build_one_func(self:&mut Self, func: &SimpFunc, is_cpp: bool) -> Result<(), &str>{
121		let mut args_c = Vec::new();
122		let mut args_r = Vec::new();
123		let mut args_usage = Vec::new();
124		let mut fn_name =  if let Some(pos) = func.fn_name.rfind("::") {
125			func.fn_name[pos + 2..].to_string()
126		}else{
127			func.fn_name.to_string()
128		};
129		if !func.klsname.is_empty() {
130			args_c.push("this__: *const u8".to_string());
131			args_r.push(format!("this__: CPtr<{}>", &func.klsname));
132			args_usage.push("this__.addr as *const u8".to_string());
133			fn_name = format!("{}__{}", &func.klsname, &func.fn_name);
134		}
135		let return_code_r = if func.is_async {
136			if func.ret.tp.is_empty() {
137				return Err("async function must have a return type");
138			}
139			if ! func.ret.tp_wrap.is_empty() {
140				self.err_str = format!("currently async function should only return non-generic types. wrap your type into a struct if need. unsupported return type {}", &func.ret.raw_str);
141				return Err(&self.err_str);
142			}
143			args_c.push("addr: usize".to_string());
144			args_usage.push("dyn_fv_addr".to_string());
145			format!(" -> {}", func.ret.tp_full)
146		} else {
147			match &func.ret.tp_wrap as &str {
148				"" if func.ret.tp.is_empty() => String::new(),
149				"POD" => format!(" -> {}", func.ret.tp),
150				_ => format!(" -> {}", func.ret.tp_full),
151			}
152		};
153
154		enum RetKind {
155			RtPrimitive,
156			RtCPtr,
157			RtSharedPtr,
158			RtObject,
159		}
160		let is_a64 = cfg!(target_arch="aarch64");
161		let mut ret_indirect = String::new();
162		let mut ret_kind = RetKind::RtPrimitive;
163		let return_code_c = match &func.ret.tp_wrap as &str {
164			"CPtr" => {
165				ret_kind = RetKind::RtCPtr;
166				" -> *const u8".to_string()
167			},
168			"SharedPtr"|"UniquePtr" => {
169				ret_kind = RetKind::RtSharedPtr;
170				if is_a64 {
171					self.asm_used = true;
172					ret_indirect = format!("let __rtox8 = &mut __rto as *mut {} as *mut u8;\n\t\t", &func.ret.tp_full);
173				} else {
174					args_c.push("__rto: * mut u8".to_string());
175					args_usage.push(format!("&mut __rto as *mut {} as *mut u8", &func.ret.tp_full));
176				}
177				"".to_string()
178			},
179			"" if func.is_async => String::new(),
180			"" if func.ret.tp.is_empty() => String::new(),
181			"" if func.ret.is_primitive => format!(" -> {}", func.ret.tp),
182			""|"POD"|"Vec" => {
183				ret_kind = RetKind::RtObject;
184				if is_a64 {
185					self.asm_used = true;
186					ret_indirect = "let __rtox8 = &mut __rta as *mut usize;\n\t\t".to_string();
187				} else {
188					args_c.push("__rto: * mut usize".to_string());
189					args_usage.push("&mut __rta as *mut usize".to_string());
190				}
191				"".to_string()
192			}
193			_ => {
194				self.err_str = format!("return type {} not supported", &func.ret.raw_str);
195				return Err(&self.err_str);
196			}
197		};
198
199		for arg in &func.arg_list {
200			let mut args_x_done = false;
201			let is_ref = arg.tp_full.chars().next().unwrap() == '&';
202			match arg.tp_wrap.as_str() {
203				""|"POD" if is_ref => {
204					match arg.tp.as_str() {
205						"CStr" => {
206							args_x_done = true;
207							args_c.push(format!("{}: *const i8", &arg.name));
208							args_r.push(format!("{}: &CStr", &arg.name));
209							args_usage.push(format!("{}.as_ptr()", &arg.name))
210						},
211						"str" => {
212							args_x_done = true;
213							args_c.push(format!("{}: *const u8, {}_len: usize", &arg.name, &arg.name));
214							args_r.push(format!("{}: &str", &arg.name));
215							args_usage.push(format!("{}.as_ptr()", &arg.name));
216							args_usage.push(format!("{}.len()", &arg.name));
217						},
218						"[u8]" => {
219							args_x_done = true;
220							args_c.push(format!("{}: *const u8, {}_len: usize", &arg.name, &arg.name));
221							args_r.push(format!("{}: &[u8]", &arg.name));
222							args_usage.push(format!("{}.as_ptr()", &arg.name));
223							args_usage.push(format!("{}.len()", &arg.name));
224						},
225						_ => args_usage.push(format!("{} as *{} {}", &arg.name, select_val(arg.is_const, "const", "mut"), &arg.tp)),
226					}
227				},
228				"CPtr" => args_usage.push(format!("{}.addr as * const u8", &arg.name)),
229				"Option" => match is_ref {
230					true => args_usage.push(format!("{}.as_ref().map_or(0 as * const {}, |x| x as * const {})", &arg.name, &arg.tp, &arg.tp)),
231					false => args_usage.push(format!("{}.map_or(0 as * const {}, |x| x as * const {})", &arg.name, &arg.tp, &arg.tp)),
232				},
233				// this is not tested, normally you should use CPtr<xx> for arguments, don't use these.
234				"SharedPtr"|"UniquePtr" => args_usage.push(format!("{}.as_cptr().addr as * const {}", &arg.name, &arg.tp)),
235				_ if arg.is_primitive => args_usage.push(format!("{}", &arg.name)),
236				_ => {
237					let suggested_str = arg.raw_str.replace(":", ": &");
238					self.err_str = format!("function \"{}\" argument \"{}\" not supported, \
239					you should always use a reference for non-primitive types in interop functions.\n\
240					try use \"{}\" instead.", func.fn_name, &arg.raw_str, &suggested_str);
241					return Err(&self.err_str);
242				}
243			};
244			if !args_x_done {
245				args_c.push(format!("{}: {}", &arg.name, &arg.tp_asc));
246				args_r.push(format!("{}: {}", &arg.name, &arg.tp_full));
247			}
248		}
249
250		let link_name = if func.is_async {
251			let sa = SimpArg{
252				name: "dyn_fv_addr".to_string(),
253				tp: "usize".to_string(),
254				tp_full: "usize".to_string(),
255				tp_wrap: "".to_string(),
256				tp_cpp: format!("ValuePromise<{}>*", func.ret.tp_cpp),
257				is_const: false,
258				is_primitive: true,
259				raw_str: "usize".to_string(),
260				tp_asc: "usize".to_string()
261			};
262			let mut func1 = func.clone();
263			func1.ret.tp = "".to_string();
264			func1.ret.tp_cpp = "".to_string();
265			func1.ret.is_primitive = true;
266			func1.arg_list.insert(0, sa);
267			self.get_link_name(&func1, is_cpp)?
268		} else {
269			self.get_link_name(&func, is_cpp)?
270		};
271		let fnstart = format!("{} {}fn {}({}){}", &func.access,
272			if func.is_async { "async " } else { "" },
273			&fn_name, args_r.join(", "), return_code_r);
274		self.extc_code += &format!("\t#[link_name = \"{link_name}\"]\n\tfn ffi__{fn_name}({}){};\n",
275			args_c.join(", "), return_code_c);
276		match ret_kind {
277			RetKind::RtPrimitive => {},
278			_ => {
279				if let Err(s) = self.show_dtor(&func.ret.tp, &func.ret.tp_wrap, &func.ret.tp_cpp) {
280					self.err_str = s.to_string();
281					return Err(&self.err_str);
282				}
283			}
284		}
285		if ! ret_indirect.is_empty() {
286			// \ asm!(\"mov x8, {{xval1}}\", xval1 = in(reg) __rtox8);
287			//asm!(\"mov x8, {{x}}\", x = in(reg) __rtox8);
288			if args_usage.len() > 0 {
289				let idx = args_usage.len() - 1;
290				let s0 = args_usage[idx].as_str();
291				let s0 = format!("{{let __argk={}; asm!(\"mov x8, {{xval1}}\", xval1=in(reg) __rtox8); __argk}}", s0);
292				args_usage[idx] = s0;
293			} else {
294				let s0 = "asm!(\"mov x8, {xval1}\", xval1=in(reg) __rtox8);\n\t\t";
295				ret_indirect += s0;
296			}
297		}
298		let usage = args_usage.join(", ");
299		let norm_code = match ret_kind {
300			RetKind::RtPrimitive if func.is_async => {
301				format!("let mut fv= FutureValue::<{}>::default();\n\
302					unsafe {{ let dyn_fv_addr = fv.to_ptr(); ffi__{fn_name}({usage}); }}\n\
303					fv.await", &func.ret.tp)
304			},
305			RetKind::RtPrimitive => format!("unsafe {{ ffi__{fn_name}({usage}) }}"),
306			RetKind::RtCPtr => format!("CPtr{{ addr: unsafe {{ ffi__{fn_name}({usage}) as usize }}, _phantom: std::marker::PhantomData }}"),
307			RetKind::RtSharedPtr => {
308				let wrap1 = &func.ret.tp_wrap as &str;
309				let ret_type = &func.ret.tp as &str;
310				format!("let mut __rto = {wrap1}::<{ret_type}>::default();\n\
311					\tunsafe {{ {ret_indirect} ffi__{fn_name}({usage}); }}\n\
312					\t__rto")
313			},
314			RetKind::RtObject => {
315				let mut ret_type = &func.ret.tp_full as &str;
316				let tp1 = func.ret.tp_cpp.replace("<", "_").replace(">", "_");
317				let call_free = match func.ret.tp_wrap.as_str() {
318					"POD" => {
319						ret_type = &func.ret.tp as &str;
320						"".to_string()
321					},  // no destructor for POD
322					_ => format!("ffi__free_{}(&mut __rta as *mut usize);\n\t\t", &tp1),
323				};
324				format!("const SZ:usize = (std::mem::size_of::<{ret_type}>()+16)/8;\n\
325					\tlet mut __rta : [usize;SZ] = [0;SZ];\n\
326					\tunsafe {{ {ret_indirect}\n\
327					\t\tffi__{fn_name}({usage}); \n\
328					\t\tlet __rto = (*(&__rta as *const usize as *const {ret_type})).clone();\n\
329					\t\t{call_free}__rto\n\
330					\t}}")
331			},
332			// _ => return Err("xx")
333		};
334		self.norm_code += &format!("{fnstart} {{\n\t{norm_code}\n}}\n");
335		Ok(())
336	}
337
338	pub fn build_bridge_code(self: &mut Self, input: TokenStream) -> Result<TokenStream, &str> {
339		let mut xxx = Functions::new();
340		if let Err(s) = xxx.parse_ts(input) {
341			self.err_str = s.to_string();
342			return Err(&self.err_str);
343		}
344
345		for func in &xxx.funcs {
346			if let Err(_) = self.build_one_func(func, xxx.is_cpp) {
347				return Err(&self.err_str);
348			}
349		}
350		let extc_code = move_obj(&mut self.extc_code);
351		let norm_code = move_obj(&mut self.norm_code);
352		let use_asm = select_val(self.asm_used, "use std::arch::asm;\n", "");
353		let all_code = format!("{use_asm}extern \"C\" {{\n{extc_code}}}\n{norm_code}\n");
354		if env_as_bool("RUST_BRIDGE_DEBUG") {
355			println!("{}", all_code);
356		}
357		TokenStream::from_str(&all_code).map_err(|e| {
358			self.err_str = e.to_string();
359			self.err_str.as_str()
360		})
361	}
362}
363
364extern "C" {
365	fn enable_msvc_debug_c();
366}
367
368#[proc_macro_attribute]
369pub fn bridge(args: TS0, input: TS0) -> TS0 {
370	let mut flags = HashSet::new();
371	for tt in args.into_iter() {
372		if let TokenTree::Ident(val) = tt {
373			flags.insert(val.to_string());
374		}
375	}
376	let mut bb = FFIBuilder::new(! flags.contains("goon") );
377	match bb.build_bridge_code(input.into()) {
378		Ok(code) => code.into(),
379		Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error().into()
380	}
381}
382
383#[proc_macro_attribute]
384pub fn enable_msvc_debug(args: TS0, _input: TS0) -> TS0
385{
386	let enable_ = if let Some(TokenTree::Ident(val)) = args.into_iter().next() {
387		val.to_string().parse::<i32>().unwrap_or(-1)
388	} else {
389		-1
390	};
391	let is_debug = match enable_ {
392		0 => false,
393		1 => true,
394		_ => {
395			match env::var("OUT_DIR") {
396				Ok(profile) => Regex::new(r"[\\/]target[\\/]debug[\\/]").unwrap().is_match(&profile),
397				Err(_) => false,
398			}
399		}
400	};
401	if is_debug {
402		unsafe{ enable_msvc_debug_c(); }
403	}
404	TS0::new()
405}