Import_as_ref and retain/release

Hi,

I'm trying to import the C++ type mlir.MLIRContext as a reference. Previously, I was able to do this via import_as_ref but after updating to a more recent version of the compiler I apparently need to specify a retain and release function as well. When I try to do this, it seems these functions must be part of the API I am importing, since trying to define a function I implement later results in a "cannot find release function" (I am trying to import MLIR without making C++-interop-specific changes to MLIR, so I don't want to create such functions in-source). Ideally, I would just want these to be no-ops since MLIRContext is a global-ish thing that I manually destroy when I no longer need it.

Any suggestions on the right path forward?

You can use immortal instead of a method name for retain/release annotations, e.g.

__attribute__((swift_attr("retain:immortal")))
__attribute__((swift_attr("release:immortal")))

That would match the semantics of MLIRContext, I think, since from the point of view of Swift this object is never destroyed.

1 Like

Thanks, this fixed the issue, but for some reason I can't access the MLIRContext initializer now due to the following error: 'mlir.MLIRContext' cannot be constructed because it has no accessible initializers

Is this related to this change or is this something else? Is there something fundamentally stopping me from creating "immortal" objects?

Yeah, there's no way to create instances of immortal types from Swift: it is assumed that they are created and managed by C++ code.

I thought mlir.MLIRContext is similar to ASTContext in a sense that it is created once for the whole duration of the program. If you need to create instances of mlir.MLIRContext, then I was wrong, this approach won't work. I don't know what's a good way to expose this class to Swift right now in that case. (cc @zoecarver)

You can just write a little C++ function that uses new to allocate and construct the mlir.MLIRContext. Otherwise, maybe you can do it entirely from swift like this:

import std.memory

let ctx = std.allocator<MLIRContext>().allocate(1)
std.construct_at(ctx, ...)

No idea if that will work.

I’m confused as to why C++ interop would treat a constructor and a function like create differently. Why can’t we just import constructors for “immortal” types?

Also, you can destroy an MLIRContext, which will invalidate any MLIR types and attributes created using that context. In my mind, this is just a matter of the developer manually making sure that the context outlives any values that utilize it. Is this correct? Or are there optimizations that depend on the assumption that a reference to an immortal value is valid forever?

A foreign reference type must always have one level of indirection. That means, it always must be used as a pointer. If a constructor returned a pointer to the foreign reference type, that would be fine. But constructors can't do that, so you have to define a create function. Does that make sense?

I added an example to the roadmap which highlights this: swift/Roadmap.md at f12678d4708e7d64d1b0ded8a0fb404baa1ce76e · apple/swift · GitHub

1 Like

Yeah this make sense. I was more referring to the semantics of the new operator. I'd wager that there exist many foreign reference types which have useful constructors and for which it would be onerous to manually bridge all constructors. Could we have the Swift expression MyForeignReferenceType(…) essentially translate to the C++ expression new MyForeignReferenceType(…) for import_as_ref types? This seems better than just dropping all constructors. We could also gate this on another attribute like initializer:new.