On Sun, Jun 21, 2015 at 8:13 AM, Nick Wellnhofer <[email protected]> wrote:
> Here’s a implementation sketch for Go:
>
>     https://github.com/nwellnhof/cfish_interfaces

OK, it took me a bit to wrap my head around this.  I think I've got it, though
I couldn't figure out how to run it.  (For some reason, "go run main.go"
doesn't pick up on the clownfish.c file sitting right there and so fails with
a ton of linker errors.)

FWIW I've included a brainlog below my sig.

> On the Clownfish side, interface objects are implemented as a struct that is
> passed by value. This struct is a pair of a Clownfish Obj and a pointer to
> an ITable containing the interface method pointers.

I follow.

> An alternative is the
> approach inspired by C++ that I originally mentioned in CLOWNFISH-12.

I'm not a fan of the approach from the Stroustrup paper where casting "self"
returns a pointer with a different value -- it is just too complicated.

IMO the true alternative is to hang the itable off of the vtable; `self`
stays the same when casting, and we have to accept a penalty of either wasting
space or slower interface method dispatch.

> There’s a new class HostObj wrapping a host object on the Clownfish side.
> Implementing interfaces as tuples makes it possible to use a single class
> for host objects.

Right, got it.  Neat!

For type safety, should every a conversion function be autogenerated for every
declared class/interface combo?  For instance, if Int declares that it
implements Comparable, this would be generated by CFC:

    static inline cfish_Comparable
    cfish_Int_AS_Comparable(cfish_Int *self) {
        cfish_Comparable comparable
            = {(cfish_Obj*)self, CFISH_INT_CFISH_COMPARABLE};
        return comparable;
    }

> Clownfish interface objects are converted to Go by storing a pointer to a Go
> conversion function in the ITable.

Gotcha, though if I understand correctly, it looks like the first argument to
those conversion functions is an Obj*, not the two-slot interface struct.

I think you could use `//export` and store a C function pointer in the ITable
instead.  See the current approach to initialization of error handling:

https://github.com/apache/lucy-clownfish/blob/61067b8539bebef4172d16e84d06ad9a08a08632/runtime/go/clownfish/clownfish.go#L45

> I also implemented a simple object registry to pass Go objects safely to C.

Interesting that host_obj ostensibly stores a void*, but you're passing it an
integer array tick.

> The biggest problem I’m facing is the conversion of wrapped Clownfish Objs
> to Clownfish interface objects on the host language side. With Go, I’m
> currently using a conversion method added to each interface. This is very
> efficient but it requires a bit of boilerplate for pure Go interfaces and
> it’s not directly portable to dynamically typed languages. It would be great
> if this could be solved on the Clownfish side. But I couldn’t find a
> solution that doesn’t require an expensive lookup.

I don't quite understand.  (If I thought for a while longer I probably could
figure out what you mean, but my reply has been delayed long enough.)

> It should also be possible to support default methods. This complicates
> things a little and it might require boilerplate in languages that don’t
> have native support like Go or Java 7.

Indeed.  Each "default" method would require a concrete wrapper for each
implementing class in such languages.  However, it's such a powerful feature
that I think that's probably an acceptable cost.

Marvin Humphrey

**********************************************

Brainlog of cfish_interfaces at 5b84f21ee
=========================================

Notes on clownfish.h
--------------------

The Dummy struct has 3 members:

*   `ref_count`
*   `klass`
*   `host_obj`

What's `host_obj`?  A Go object pointer?  Will that introduce a race condition
with the compiler?

The ITable has two members.

*   `to_host`
*   `vtable`

The `vtable` ivar is straightforward.  But what about `to_host`?  A Go
function pointer?  We'll have to see.

The refcount manipulation routines `inc_refcount` and `dec_refcount` are
straightforward.  So are `cfish_method`, `cfish_obj_method`, and
`bootstrap_parcel`.

So `HostObj_new` returns a pointer.  It's not a struct?  What has two slots?

OK, so the Comparable interface struct has 2 slots.  I guess this means that
we'll have separate struct types for each Clownfish interface.

Interesting that there's a clash between `Comparable_Compare_To_t` and
`Comparable_Compare_To` -- one takes an `Obj*` as its invocant and the other
takes a Comparable.

It makes sense that we'll have an autogenerated ITable whose name
combines the name of the interface and the name of the implementing class.

Everything about the class Int is straightforward.  The only new aspect is the
presence of the `COMPARABLE_INT` ITable.

Notes on clownfish.c
--------------------

It's surprising that the Obj struct does not have a `host_obj` ivar like Dummy
does.  I see that HostObj does, though -- so Dummy represents a HostObj rather
than an Obj as in ordinary Clownfish.

Interesting that the ITables are bootstrapped at runtime.

Other than that, everything makes sense.

Notes on main.go
----------------

Why do we need the `clownfish_func` wrapper -- isn't `Comparable_Compare_To`
a static inline function?  CGO can't deal with macros, but I thought it could
handle static inline functions.

All the stuff in ObjIMP makes sense.

So if I understand Store() right, it looks like it casts a small integer array
tick to a `void*` in order to store it as `host_obj`?  When skimming I was
taken aback by Fetch() which used the pointer value stored in `host_obj` to
index into the entries array -- but if it's just an array tick, that's fine.
I presume that in other languages, it will actually be the address of a host
object.

I don't quite grok Store() line-by-line, but it seems to me that the
fundamental design is compatible with compacting GC.

One thing to look out for is that Go's `int` is not guaranteed to be the same
width as a C `int`.

OK, I see that `wrap_HostObj_Comparable` and `wrap_Int_Comparable` are
intended to be called from C.  FWIW I would have used `//export` and C
function pointers -- see how the error handling is bootstrapped in the Go
bindings.  But it's ugly and convoluted no matter what.

Wait, `Comparable_Compare_To_HOSTIMP` isn't used... oh!  It's exported and
called only from C.

Aha!  In test(), now I see why you needed the `clownfish_func` wrapper inside
of CallClownfish() -- to force the comparison to happen through Clownfish
channels.

Reply via email to