Thread overview
object - interface compatibility
Nov 14, 2006
BCS
Nov 14, 2006
BCS
Nov 14, 2006
Pragma
Nov 14, 2006
BCS
Nov 14, 2006
Walter Bright
November 14, 2006
Today I had a hard lesson to learn. An iface and an object is not exchangeable. The reference to an object is manipulated if it is casted to an interface type.

This happens implicit if an object is passed to a var/meth which wants it to be type of iface.

But this does not happen implicit if you want to pass and array of
objects. This is, because it would mean to iterate over the array, cast
piece-wise and build a new array.
But the consequence is, that the user of the class has to distinguish
between class types and iface types.

I wonder if this should really be in that way?
Is this documented?
November 14, 2006
Frank Benoit (keinfarbton) wrote:
> Today I had a hard lesson to learn. An iface and an object is not
> exchangeable. The reference to an object is manipulated if it is casted
> to an interface type.
> 
> This happens implicit if an object is passed to a var/meth which wants
> it to be type of iface.
> 
> But this does not happen implicit if you want to pass and array of
> objects. This is, because it would mean to iterate over the array, cast
> piece-wise and build a new array.
> But the consequence is, that the user of the class has to distinguish
> between class types and iface types.
> 
> I wonder if this should really be in that way?
> Is this documented?

The convention from an object to an interface is unavoidable (at some point). Making this conversion implicit would help but, could get costly.


interface I{}
class C : I{}

foo(I[] arr){}



fig(C[] arr){}
{
	foo(cast(I[])arr);
}


void main()
{
	C[] blah = new C[1000000];

	fig(blah); // Ouch O(n) cost in a cast
}



you can neaten up code a bit with something like this:

If[] acast(If, Cl)(Cl[] arr)
{
	If[] ret = new If[arr.length];
	foreach(i,e;arr)
		ret[i] = cast(If)arr[i];

	return ret;
}
November 14, 2006
Yes, thanks for you example.

To make my question more clear:

1.) Is this reference manipulation really absolutely necessary? Why? 2.) Isn't there a way to make class and iface really compatible? 3.) Please document this very cleanly in the D spec.

Frank
November 14, 2006
Frank Benoit (keinfarbton) wrote:
> Yes, thanks for you example.
> 
> To make my question more clear:
> 
> 1.) Is this reference manipulation really absolutely necessary? Why?
> 2.) Isn't there a way to make class and iface really compatible?
> 3.) Please document this very cleanly in the D spec.
>
> Frank

In short, No. The long version is that while it could be done, it would add a lot of overhead to everything and only fix a problem that is run into occasionally.

Actually I ran into this difference a while back, what it amounts to (if I understand it correctly) is that the calling convention for interfaces is different than that for objects.

For object it amounts to some variation on this (in sudo ASM)

//obj.test(a, b, c);

push c
push b
push a	// push args

mov obj -> r1	// load pointer to object

mov *r1 -> r2	// load v-tbl

push r1		// push "this"

call r2[test]	// use v-tbl to call test




for an interface this is about what is done


//inf.test(a, b, c);

push c
push b
push a	// push args

mov obj -> r1	// load pointer to object

mov *r1 -> r2	// load v-tbl

		// adjust interface to point to object
		// uses information from interface v-tbl
add r1 r2[__offset] -> r1

push r1		// push "this"

call *(r2+test)	// use v-tbl to call test


the interface calling convention adds one more memory fetch and an add. Not a lot but it does add to the overhead and complexity that D's single inheritance model is trying to avoid.


Actually I would like to see interface calling convention changed to use a fat pointer for interfaces. This would use a arbitrary context pointer welded to a pointer to a v-tbl. All sorts of cool things could be done with this.

interface literals working like delegate literals
3rd party interface implementation for classes*
interfaces from structs, arrays and any arbitrary pointers.

In effect any reference can be used as a context and as the basis for an interface


<D version >= 2.0 feature suggestion>

(*)

something like this

// third party closed source code
class Foo
{
	int baz();
	char bar();
	void go(int i);
}


// your code
interface I
{
	void Go();
	void Stop();
}


weld Baz : [Foo:I]
{
	void Go()
	{
		this.go(1);
	}
	void Stop()
	{
		this.go(0);
	}
}


void func(I);

void main()
{
	auto f = new Foo;
	func(f);	// fails (Foo cant be converted to I)
	func(Baz(f));	// works
}

Baz is of type I, when a Foo is converted to a Baz all that needs to happen is join the Foo pointer and Baz's v-tbl pointer.
November 14, 2006
BCS wrote:
> Actually I would like to see interface calling convention changed to use a fat pointer for interfaces. This would use a arbitrary context pointer welded to a pointer to a v-tbl. All sorts of cool things could be done with this.
> 
> interface literals working like delegate literals
> 3rd party interface implementation for classes*
> interfaces from structs, arrays and any arbitrary pointers.
> 
> In effect any reference can be used as a context and as the basis for an interface

So then the interface method call turns into:

//inf.test(a, b, c);
push c
push b
push a    // push args

mov obj -> r1    // load pointer to object
mov vtbl -> r2    // load v-tbl (obj+vtbl = fat-pointer)
push r1        // push "this"
call r2[test]    // use v-tbl to call test

So, this basically sacrifices stack space in favor of a few less opcodes per interface-method call. :)

But doesn't this make cast() operations *longer* by one additional move operation?  Are method calls really more numerous than casts?

On the up-side, it would only make the ABI more consistent, since delegates are pretty much doing the same thing when compared to their function-pointer counterpart (as you already mentioned).  A higher degree of consistency there, could buy us a higher degree of consistency with D's constructs, cleaner code, and possibly more-bug-free compiler implementations.

-- 
- EricAnderton at yahoo
November 14, 2006
Frank Benoit (keinfarbton) wrote:
> 1.) Is this reference manipulation really absolutely necessary? Why?

There are two ways to implement interfaces. One is where the determination of what functions to call is done by repeated testing of an object to see what interfaces it supports. This is slow. The other way is to do it like C++ does multiple inheritance. Interfaces are really "multiple inheritance lite", and D does it the C++ way, which is very efficient at runtime.

> 2.) Isn't there a way to make class and iface really compatible?

Consider a function that takes an interface as an argument. Two different objects, A and B, can implement that interface. How can the function tell that it can call A.foo() when it gets an interface derived from A, when B doesn't have a .foo()?

> 3.) Please document this very cleanly in the D spec.
November 14, 2006
Pragma wrote:
> BCS wrote:
>> Actually I would like to see interface calling convention changed to use a fat pointer for interfaces. This would use a arbitrary context pointer welded to a pointer to a v-tbl. All sorts of cool things could be done with this.
>>
[...]
> 
> So then the interface method call turns into:
> 
> //inf.test(a, b, c);
> push c
> push b
> push a    // push args
> 
> mov obj -> r1    // load pointer to object
> mov vtbl -> r2    // load v-tbl (obj+vtbl = fat-pointer)
> push r1        // push "this"
> call r2[test]    // use v-tbl to call test
> 

More or less.

> So, this basically sacrifices stack space in favor of a few less opcodes per interface-method call. :)
> 
> But doesn't this make cast() operations *longer* by one additional move operation?  Are method calls really more numerous than casts?
> 

That is a problem, it doubles the size of an interface reference. I can see reasons why that would be bad. (BTW this doesn't attempt to solve the object-interface incompatibility.)

OTOH It may make objects smaller by one word for each interface they implement. I think that the v-tbl pointer for interfaces is part of the object, with the proposed system, it would be part of the objects v-tbl.

It all depends on what your looking for, this system is a bit bigger in one place, a bit smaller in another and provides a few new options for features.

> On the up-side, it would only make the ABI more consistent, since delegates are pretty much doing the same thing when compared to their function-pointer counterpart (as you already mentioned).  A higher degree of consistency there, could buy us a higher degree of consistency with D's constructs, cleaner code, and possibly more-bug-free compiler implementations.
>