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.
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
):
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"):
62 self
.fd
.write(" " * indent
+ s
+ "\n")
64 if c
== "(" or c
== "{" or c
== "[":
66 elif c
== ")" or c
== "}" or c
== "]":
130 # printdoccomments = False
131 printdoccomments
= True
135 def printComments(fd
, clist
, indent
):
136 fd
.write("%s%s" % (indent
, doccomments(clist
)))
138 def doccomments(clist
):
143 for cc
in c
.splitlines():
145 s
+= "\n/// ```\n///\n"
150 def printComments(fd
, clist
, indent
):
153 def doccomments(clist
):
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
):
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
:
183 l
= [(attributeParamName(a
), a
.realtype
.rustType("out" if getter
else "in"))]
184 if a
.implicit_jscontext
:
185 raise xpidl
.RustNoncompat("jscontext is unsupported")
187 raise xpidl
.RustNoncompat("nostdcall is unsupported")
191 def attributeParamList(iface
, a
, getter
):
192 l
= ["this: *const " + iface
.name
]
193 l
+= ["%s: %s" % x
for x
in attributeRawParamList(iface
, a
, getter
)]
197 def attrAsVTableEntry(iface
, m
, getter
):
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
:
206 /// Unable to generate binding because `%s`
207 pub %s: *const ::libc::c_void""" % (
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
):
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")
232 raise xpidl
.RustNoncompat("optional_argc is unsupported")
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")))
243 def methodParamList(iface
, m
):
244 l
= ["this: *const %s" % iface
.name
]
245 l
+= ["%s: %s" % x
for x
in methodRawParamList(iface
, m
)]
249 def methodAsVTableEntry(iface
, m
):
251 return 'pub %s: unsafe extern "system" fn (%s) -> %s' % (
253 methodParamList(iface
, m
),
256 except xpidl
.RustNoncompat
as reason
:
258 /// Unable to generate binding because `%s`
259 pub %s: *const ::libc::c_void""" % (
265 method_impl_tmpl
= """\
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
):
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
= """\
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());
302 def attrAsWrapper(iface
, m
, getter
):
304 if m
.implicit_jscontext
:
305 raise xpidl
.RustNoncompat("jscontext is unsupported")
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
)
336 // DO NOT EDIT. THIS FILE IS GENERATED FROM $SRCDIR/%(relpath)s
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
):
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
356 for p
in idl
.productions
:
357 if p
.kind
== "include" or p
.kind
== "cdata" or p
.kind
== "forward":
360 if p
.kind
== "interface":
361 write_interface(p
, fd
)
364 if p
.kind
== "typedef":
366 # We have to skip the typedef of bool to bool (it doesn't make any sense anyways)
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
:
379 "/* unable to generate %s typedef because `%s` */\n\n"
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,
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
400 pub struct %(name)sVTable {%(base)s%(entries)s}
405 # NOTE: This template is not generated for nsISupports, as it has no base interfaces.
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;
414 fn deref(&self) -> &%(base)s {
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 {
425 fn coerce_from(v: &%(name)s) -> &Self {
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.
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
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
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,
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 {
477 unsafe fn addref(&self) {
481 unsafe fn release(&self) {
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.
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 {
498 fn coerce_from(v: &%(name)s) -> &Self {
504 /// Cast this `%(name)s` to one of its base interfaces.
506 pub fn coerce<T: %(name)sCoerce>(&self) -> &T {
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 {}
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.
532 vtable_entry_tmpl
= """\
538 const_wrapper_tmpl
= """\
540 pub const %(name)s: %(type)s = %(val)s;
544 method_wrapper_tmpl
= """\
551 uuid_decoder
= re
.compile(
552 r
"""(?P<m0>[a-f0-9]{8})-
556 (?P<m4>[a-f0-9]{12})$""",
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
574 if iface
.base
is not None:
575 fd
.write("/// `interface %s : %s`\n///\n" % (iface
.name
, iface
.base
))
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:
594 for member
in iface
.members
:
595 if type(member
) == xpidl
.Attribute
:
599 "idl": member
.toIDL(),
600 "entry": attrAsVTableEntry(iface
, member
, True),
603 if not member
.readonly
:
607 "idl": member
.toIDL(),
608 "entry": attrAsVTableEntry(iface
, member
, False),
612 elif type(member
) == xpidl
.Method
:
616 "idl": member
.toIDL(),
617 "entry": methodAsVTableEntry(iface
, member
),
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
632 for member
in iface
.members
:
633 if type(member
) == xpidl
.ConstMember
:
637 "docs": doccomments(member
.doccomments
),
638 "type": member
.realtype
.rustType("in"),
640 "val": member
.getValue(),
643 if type(member
) == xpidl
.CEnum
:
644 for var
in member
.variants
:
649 "type": member
.rustType("in"),
651 "val": var
.getValue(),
656 for member
in iface
.members
:
657 if type(member
) == xpidl
.Attribute
:
661 "docs": doccomments(member
.doccomments
),
662 "idl": member
.toIDL(),
663 "wrapper": attrAsWrapper(iface
, member
, True),
666 if not member
.readonly
:
670 "docs": doccomments(member
.doccomments
),
671 "idl": member
.toIDL(),
672 "wrapper": attrAsWrapper(iface
, member
, False),
676 elif type(member
) == xpidl
.Method
:
680 "docs": doccomments(member
.doccomments
),
681 "idl": member
.toIDL(),
682 "wrapper": methodAsWrapper(iface
, member
),
690 "consts": "\n".join(consts
),
691 "methods": "\n".join(methods
),