Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / idl-parser / xpidl / rust.py
blobb9fc6627fbfa7633c3e1ebf4a5e393f21b13b4ec
1 # rust.py - Generate rust bindings from IDL.
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 """Print a runtime Rust bindings file for the IDL file specified"""
9 # --- Safety Hazards ---
11 # We currently don't generate some bindings for some IDL methods in rust code,
12 # due to there being ABI safety hazards if we were to do so. This is the
13 # documentation for the reasons why we don't generate certain types of bindings,
14 # so that we don't accidentally start generating them in the future.
16 # notxpcom methods and attributes return their results directly by value. The x86
17 # windows stdcall ABI returns aggregates by value differently for methods than
18 # functions, and rust only exposes the function ABI, so that's the one we're
19 # using. The correct ABI can be emulated for notxpcom methods returning aggregates
20 # by passing an &mut ReturnType parameter as the second parameter. This strategy
21 # is used by the winapi-rs crate.
22 # https://github.com/retep998/winapi-rs/blob/7338a5216a6a7abeefcc6bb1bc34381c81d3e247/src/macros.rs#L220-L231
24 # Right now we can generate code for notxpcom methods and attributes, as we don't
25 # support passing aggregates by value over these APIs ever (the types which are
26 # allowed in xpidl.py shouldn't include any aggregates), so the code is
27 # correct. In the future if we want to start supporting returning aggregates by
28 # value, we will need to use a workaround such as the one used by winapi.rs.
30 # nostdcall methods on x86 windows will use the thiscall ABI, which is not
31 # stable in rust right now, so we cannot generate bindings to them.
33 # In general, passing C++ objects by value over the C ABI is not a good idea,
34 # and when possible we should avoid doing so. We don't generate bindings for
35 # these methods here currently.
37 import os.path
38 import re
40 from xpidl import xpidl
43 class AutoIndent(object):
44 """A small autoindenting wrapper around a fd.
45 Used to make the code output more readable."""
47 def __init__(self, fd):
48 self.fd = fd
49 self.indent = 0
51 def write(self, string):
52 """A smart write function which automatically adjusts the
53 indentation of each line as it is written by counting braces"""
54 for s in string.split("\n"):
55 s = s.strip()
56 indent = self.indent
57 if len(s) == 0:
58 indent = 0
59 elif s[0] == "}":
60 indent -= 1
62 self.fd.write(" " * indent + s + "\n")
63 for c in s:
64 if c == "(" or c == "{" or c == "[":
65 self.indent += 1
66 elif c == ")" or c == "}" or c == "]":
67 self.indent -= 1
70 def rustSanitize(s):
71 keywords = [
72 "abstract",
73 "alignof",
74 "as",
75 "become",
76 "box",
77 "break",
78 "const",
79 "continue",
80 "crate",
81 "do",
82 "else",
83 "enum",
84 "extern",
85 "false",
86 "final",
87 "fn",
88 "for",
89 "if",
90 "impl",
91 "in",
92 "let",
93 "loop",
94 "macro",
95 "match",
96 "mod",
97 "move",
98 "mut",
99 "offsetof",
100 "override",
101 "priv",
102 "proc",
103 "pub",
104 "pure",
105 "ref",
106 "return",
107 "Self",
108 "self",
109 "sizeof",
110 "static",
111 "struct",
112 "super",
113 "trait",
114 "true",
115 "type",
116 "typeof",
117 "unsafe",
118 "unsized",
119 "use",
120 "virtual",
121 "where",
122 "while",
123 "yield",
125 if s in keywords:
126 return s + "_"
127 return s
130 # printdoccomments = False
131 printdoccomments = True
133 if printdoccomments:
135 def printComments(fd, clist, indent):
136 fd.write("%s%s" % (indent, doccomments(clist)))
138 def doccomments(clist):
139 if len(clist) == 0:
140 return ""
141 s = "/// ```text"
142 for c in clist:
143 for cc in c.splitlines():
144 s += "\n/// " + cc
145 s += "\n/// ```\n///\n"
146 return s
148 else:
150 def printComments(fd, clist, indent):
151 pass
153 def doccomments(clist):
154 return ""
157 def firstCap(str):
158 return str[0].upper() + str[1:]
161 # Attribute VTable Methods
162 def attributeNativeName(a, getter):
163 binaryname = rustSanitize(a.binaryname if a.binaryname else firstCap(a.name))
164 return "%s%s" % ("Get" if getter else "Set", binaryname)
167 def attributeReturnType(a, getter):
168 if a.notxpcom:
169 if getter:
170 return a.realtype.rustType("in").strip()
171 return "::libc::c_void"
172 return "::nserror::nsresult"
175 def attributeParamName(a):
176 return "a" + firstCap(a.name)
179 def attributeRawParamList(iface, a, getter):
180 if getter and a.notxpcom:
181 l = []
182 else:
183 l = [(attributeParamName(a), a.realtype.rustType("out" if getter else "in"))]
184 if a.implicit_jscontext:
185 raise xpidl.RustNoncompat("jscontext is unsupported")
186 if a.nostdcall:
187 raise xpidl.RustNoncompat("nostdcall is unsupported")
188 return l
191 def attributeParamList(iface, a, getter):
192 l = ["this: *const " + iface.name]
193 l += ["%s: %s" % x for x in attributeRawParamList(iface, a, getter)]
194 return ", ".join(l)
197 def attrAsVTableEntry(iface, m, getter):
198 try:
199 return 'pub %s: unsafe extern "system" fn (%s) -> %s' % (
200 attributeNativeName(m, getter),
201 attributeParamList(iface, m, getter),
202 attributeReturnType(m, getter),
204 except xpidl.RustNoncompat as reason:
205 return """\
206 /// Unable to generate binding because `%s`
207 pub %s: *const ::libc::c_void""" % (
208 reason,
209 attributeNativeName(m, getter),
213 # Method VTable generation functions
214 def methodNativeName(m):
215 binaryname = m.binaryname is not None and m.binaryname or firstCap(m.name)
216 return rustSanitize(binaryname)
219 def methodReturnType(m):
220 if m.notxpcom:
221 return m.realtype.rustType("in").strip()
222 return "::nserror::nsresult"
225 def methodRawParamList(iface, m):
226 l = [(rustSanitize(p.name), p.rustType()) for p in m.params]
228 if m.implicit_jscontext:
229 raise xpidl.RustNoncompat("jscontext is unsupported")
231 if m.optional_argc:
232 raise xpidl.RustNoncompat("optional_argc is unsupported")
234 if m.nostdcall:
235 raise xpidl.RustNoncompat("nostdcall is unsupported")
237 if not m.notxpcom and m.realtype.name != "void":
238 l.append(("_retval", m.realtype.rustType("out")))
240 return l
243 def methodParamList(iface, m):
244 l = ["this: *const %s" % iface.name]
245 l += ["%s: %s" % x for x in methodRawParamList(iface, m)]
246 return ", ".join(l)
249 def methodAsVTableEntry(iface, m):
250 try:
251 return 'pub %s: unsafe extern "system" fn (%s) -> %s' % (
252 methodNativeName(m),
253 methodParamList(iface, m),
254 methodReturnType(m),
256 except xpidl.RustNoncompat as reason:
257 return """\
258 /// Unable to generate binding because `%s`
259 pub %s: *const ::libc::c_void""" % (
260 reason,
261 methodNativeName(m),
265 method_impl_tmpl = """\
266 #[inline]
267 pub unsafe fn %(name)s(&self, %(params)s) -> %(ret_ty)s {
268 ((*self.vtable).%(name)s)(self, %(args)s)
273 def methodAsWrapper(iface, m):
274 try:
275 param_list = methodRawParamList(iface, m)
276 params = ["%s: %s" % x for x in param_list]
277 args = [x[0] for x in param_list]
279 return method_impl_tmpl % {
280 "name": methodNativeName(m),
281 "params": ", ".join(params),
282 "ret_ty": methodReturnType(m),
283 "args": ", ".join(args),
285 except xpidl.RustNoncompat:
286 # Dummy field for the doc comments to attach to.
287 # Private so that it's not shown in rustdoc.
288 return "const _%s: () = ();" % methodNativeName(m)
291 infallible_impl_tmpl = """\
292 #[inline]
293 pub unsafe fn %(name)s(&self) -> %(realtype)s {
294 let mut result = <%(realtype)s as ::std::default::Default>::default();
295 let _rv = ((*self.vtable).%(name)s)(self, &mut result);
296 debug_assert!(_rv.succeeded());
297 result
302 def attrAsWrapper(iface, m, getter):
303 try:
304 if m.implicit_jscontext:
305 raise xpidl.RustNoncompat("jscontext is unsupported")
307 if m.nostdcall:
308 raise xpidl.RustNoncompat("nostdcall is unsupported")
310 name = attributeParamName(m)
312 if getter and m.infallible and m.realtype.kind == "builtin":
313 # NOTE: We don't support non-builtin infallible getters in Rust code.
314 return infallible_impl_tmpl % {
315 "name": attributeNativeName(m, getter),
316 "realtype": m.realtype.rustType("in"),
319 param_list = attributeRawParamList(iface, m, getter)
320 params = ["%s: %s" % x for x in param_list]
321 return method_impl_tmpl % {
322 "name": attributeNativeName(m, getter),
323 "params": ", ".join(params),
324 "ret_ty": attributeReturnType(m, getter),
325 "args": "" if getter and m.notxpcom else name,
328 except xpidl.RustNoncompat:
329 # Dummy field for the doc comments to attach to.
330 # Private so that it's not shown in rustdoc.
331 return "const _%s: () = ();" % attributeNativeName(m, getter)
334 header = """\
336 // DO NOT EDIT. THIS FILE IS GENERATED FROM $SRCDIR/%(relpath)s
342 def idl_basename(f):
343 """returns the base name of a file with the last extension stripped"""
344 return os.path.splitext(os.path.basename(f))[0]
347 def print_rust_bindings(idl, fd, relpath):
348 fd = AutoIndent(fd)
350 fd.write(header % {"relpath": relpath})
352 # All of the idl files will be included into the same rust module, as we
353 # can't do forward declarations. Because of this, we want to ignore all
354 # import statements
356 for p in idl.productions:
357 if p.kind == "include" or p.kind == "cdata" or p.kind == "forward":
358 continue
360 if p.kind == "interface":
361 write_interface(p, fd)
362 continue
364 if p.kind == "typedef":
365 try:
366 # We have to skip the typedef of bool to bool (it doesn't make any sense anyways)
367 if p.name == "bool":
368 continue
370 if printdoccomments:
371 fd.write(
372 "/// `typedef %s %s;`\n///\n"
373 % (p.realtype.nativeType("in"), p.name)
375 fd.write(doccomments(p.doccomments))
376 fd.write("pub type %s = %s;\n\n" % (p.name, p.realtype.rustType("in")))
377 except xpidl.RustNoncompat as reason:
378 fd.write(
379 "/* unable to generate %s typedef because `%s` */\n\n"
380 % (p.name, reason)
384 base_vtable_tmpl = """
385 /// We need to include the members from the base interface's vtable at the start
386 /// of the VTable definition.
387 pub __base: %sVTable,
392 vtable_tmpl = """\
393 // This struct represents the interface's VTable. A pointer to a statically
394 // allocated version of this struct is at the beginning of every %(name)s
395 // object. It contains one pointer field for each method in the interface. In
396 // the case where we can't generate a binding for a method, we include a void
397 // pointer.
398 #[doc(hidden)]
399 #[repr(C)]
400 pub struct %(name)sVTable {%(base)s%(entries)s}
405 # NOTE: This template is not generated for nsISupports, as it has no base interfaces.
406 deref_tmpl = """\
407 // Every interface struct type implements `Deref` to its base interface. This
408 // causes methods on the base interfaces to be directly avaliable on the
409 // object. For example, you can call `.AddRef` or `.QueryInterface` directly
410 // on any interface which inherits from `nsISupports`.
411 impl ::std::ops::Deref for %(name)s {
412 type Target = %(base)s;
413 #[inline]
414 fn deref(&self) -> &%(base)s {
415 unsafe {
416 ::std::mem::transmute(self)
421 // Ensure we can use .coerce() to cast to our base types as well. Any type which
422 // our base interface can coerce from should be coercable from us as well.
423 impl<T: %(base)sCoerce> %(name)sCoerce for T {
424 #[inline]
425 fn coerce_from(v: &%(name)s) -> &Self {
426 T::coerce_from(v)
432 struct_tmpl = """\
433 // The actual type definition for the interface. This struct has methods
434 // declared on it which will call through its vtable. You never want to pass
435 // this type around by value, always pass it behind a reference.
437 #[repr(C)]
438 pub struct %(name)s {
439 vtable: &'static %(name)sVTable,
441 /// This field is a phantomdata to ensure that the VTable type and any
442 /// struct containing it is not safe to send across threads by default, as
443 /// XPCOM is generally not threadsafe.
445 /// If this type is marked as [rust_sync], there will be explicit `Send` and
446 /// `Sync` implementations on this type, which will override the inherited
447 /// negative impls from `Rc`.
448 __nosync: ::std::marker::PhantomData<::std::rc::Rc<u8>>,
450 // Make the rust compiler aware that there might be interior mutability
451 // in what actually implements the interface. This works around UB
452 // introduced by https://github.com/llvm/llvm-project/commit/01859da84bad95fd51d6a03b08b60c660e642a4f
453 // that a rust lint would make blatantly obvious, but doesn't exist.
454 // (See https://github.com/rust-lang/rust/issues/111229).
455 // This prevents optimizations, but those optimizations weren't available
456 // before rustc switched to LLVM 16, and they now cause problems because
457 // of the UB.
458 // Until there's a lint available to find all our UB, it's simpler to
459 // avoid the UB in the first place, at the cost of preventing optimizations
460 // in places that don't cause UB. But again, those optimizations weren't
461 // available before.
462 __maybe_interior_mutability: ::std::cell::UnsafeCell<[u8; 0]>,
465 // Implementing XpCom for an interface exposes its IID, which allows for easy
466 // use of the `.query_interface<T>` helper method. This also defines that
467 // method for %(name)s.
468 unsafe impl XpCom for %(name)s {
469 const IID: nsIID = nsID(0x%(m0)s, 0x%(m1)s, 0x%(m2)s,
470 [%(m3joined)s]);
473 // We need to implement the RefCounted trait so we can be used with `RefPtr`.
474 // This trait teaches `RefPtr` how to manage our memory.
475 unsafe impl RefCounted for %(name)s {
476 #[inline]
477 unsafe fn addref(&self) {
478 self.AddRef();
480 #[inline]
481 unsafe fn release(&self) {
482 self.Release();
486 // This trait is implemented on all types which can be coerced to from %(name)s.
487 // It is used in the implementation of `fn coerce<T>`. We hide it from the
488 // documentation, because it clutters it up a lot.
489 #[doc(hidden)]
490 pub trait %(name)sCoerce {
491 /// Cheaply cast a value of this type from a `%(name)s`.
492 fn coerce_from(v: &%(name)s) -> &Self;
495 // The trivial implementation: We can obviously coerce ourselves to ourselves.
496 impl %(name)sCoerce for %(name)s {
497 #[inline]
498 fn coerce_from(v: &%(name)s) -> &Self {
503 impl %(name)s {
504 /// Cast this `%(name)s` to one of its base interfaces.
505 #[inline]
506 pub fn coerce<T: %(name)sCoerce>(&self) -> &T {
507 T::coerce_from(self)
513 sendsync_tmpl = """\
514 // This interface is marked as [rust_sync], meaning it is safe to be transferred
515 // and used from multiple threads silmultaneously. These override the default
516 // from the __nosync marker type allowng the type to be sent between threads.
517 unsafe impl Send for %(name)s {}
518 unsafe impl Sync for %(name)s {}
522 wrapper_tmpl = """\
523 // The implementations of the function wrappers which are exposed to rust code.
524 // Call these methods rather than manually calling through the VTable struct.
525 impl %(name)s {
526 %(consts)s
527 %(methods)s
532 vtable_entry_tmpl = """\
533 /* %(idl)s */
534 %(entry)s,
538 const_wrapper_tmpl = """\
539 %(docs)s
540 pub const %(name)s: %(type)s = %(val)s;
544 method_wrapper_tmpl = """\
545 %(docs)s
546 /// `%(idl)s`
547 %(wrapper)s
551 uuid_decoder = re.compile(
552 r"""(?P<m0>[a-f0-9]{8})-
553 (?P<m1>[a-f0-9]{4})-
554 (?P<m2>[a-f0-9]{4})-
555 (?P<m3>[a-f0-9]{4})-
556 (?P<m4>[a-f0-9]{12})$""",
557 re.X,
561 def write_interface(iface, fd):
562 if iface.namemap is None:
563 raise Exception("Interface was not resolved.")
565 assert iface.base or (iface.name == "nsISupports")
567 # Extract the UUID's information so that it can be written into the struct definition
568 names = uuid_decoder.match(iface.attributes.uuid).groupdict()
569 m3str = names["m3"] + names["m4"]
570 names["m3joined"] = ", ".join(["0x%s" % m3str[i : i + 2] for i in range(0, 16, 2)])
571 names["name"] = iface.name
573 if printdoccomments:
574 if iface.base is not None:
575 fd.write("/// `interface %s : %s`\n///\n" % (iface.name, iface.base))
576 else:
577 fd.write("/// `interface %s`\n///\n" % iface.name)
578 printComments(fd, iface.doccomments, "")
579 fd.write(struct_tmpl % names)
581 if iface.attributes.rust_sync:
582 fd.write(sendsync_tmpl % {"name": iface.name})
584 if iface.base is not None:
585 fd.write(
586 deref_tmpl
588 "name": iface.name,
589 "base": iface.base,
593 entries = []
594 for member in iface.members:
595 if type(member) == xpidl.Attribute:
596 entries.append(
597 vtable_entry_tmpl
599 "idl": member.toIDL(),
600 "entry": attrAsVTableEntry(iface, member, True),
603 if not member.readonly:
604 entries.append(
605 vtable_entry_tmpl
607 "idl": member.toIDL(),
608 "entry": attrAsVTableEntry(iface, member, False),
612 elif type(member) == xpidl.Method:
613 entries.append(
614 vtable_entry_tmpl
616 "idl": member.toIDL(),
617 "entry": methodAsVTableEntry(iface, member),
621 fd.write(
622 vtable_tmpl
624 "name": iface.name,
625 "base": base_vtable_tmpl % iface.base if iface.base is not None else "",
626 "entries": "\n".join(entries),
630 # Get all of the constants
631 consts = []
632 for member in iface.members:
633 if type(member) == xpidl.ConstMember:
634 consts.append(
635 const_wrapper_tmpl
637 "docs": doccomments(member.doccomments),
638 "type": member.realtype.rustType("in"),
639 "name": member.name,
640 "val": member.getValue(),
643 if type(member) == xpidl.CEnum:
644 for var in member.variants:
645 consts.append(
646 const_wrapper_tmpl
648 "docs": "",
649 "type": member.rustType("in"),
650 "name": var.name,
651 "val": var.getValue(),
655 methods = []
656 for member in iface.members:
657 if type(member) == xpidl.Attribute:
658 methods.append(
659 method_wrapper_tmpl
661 "docs": doccomments(member.doccomments),
662 "idl": member.toIDL(),
663 "wrapper": attrAsWrapper(iface, member, True),
666 if not member.readonly:
667 methods.append(
668 method_wrapper_tmpl
670 "docs": doccomments(member.doccomments),
671 "idl": member.toIDL(),
672 "wrapper": attrAsWrapper(iface, member, False),
676 elif type(member) == xpidl.Method:
677 methods.append(
678 method_wrapper_tmpl
680 "docs": doccomments(member.doccomments),
681 "idl": member.toIDL(),
682 "wrapper": methodAsWrapper(iface, member),
686 fd.write(
687 wrapper_tmpl
689 "name": iface.name,
690 "consts": "\n".join(consts),
691 "methods": "\n".join(methods),