July 25, 2017
On Monday, 24 July 2017 at 14:30:01 UTC, Atila Neves wrote:
> struct Foo { }
>
>
> struct S {
>
>     Foo* _foo;
>     bool _isShared;
>
>     this(this T, U)(U foo) if(is(T == shared) && is(U == shared(Foo)*) || !is(T == shared) && is(U == Foo*)) {
>         static if(is(T == shared)) _isShared = true;
>         _foo = foo;
>     }
>
>     ~this() {
>         import std.stdio: writeln;
>         _isShared ? writeln("shared dtor") : writeln("non-shared dtor");
>     }
> }
>
> void main() {
>     auto f = Foo();
>     auto sf = shared Foo();
>     auto s = S(&f);
>     auto ss = shared S(&sf);
> }

Exactly this. You must design struct to support shared type, in which case it's better and more straightforward to just write shared destructor rather than work it around. Same for immutable.
July 25, 2017
On Monday, 24 July 2017 at 20:30:25 UTC, Jonathan M Davis wrote:
> On Monday, July 24, 2017 2:30:01 PM MDT Atila Neves via Digitalmars-d wrote:
>> >> This is fine. What dmd does now is strip shared off of the `this` pointer, not the member variables. There's only a problem if the sharedness of the member variable(s) depends on sharedness of the enclosing object.
>> >
>> > What happens with something like
>> >
>> > struct S
>> > {
>> >
>> >     Foo* _foo;
>> >
>> >     ~this() {...}
>> >
>> > }
>> >
>> > shared S s;
>> >
>> > Inside the destructor, is what _foo points to still treated as
>> > shared: shared(Foo)*?
>>
>> No. This is what I meant by the sharedness depening on the enclosing object. However, there's a workaround:
>>
>> struct Foo { }
>>
>>
>> struct S {
>>
>>      Foo* _foo;
>>      bool _isShared;
>>
>>      this(this T, U)(U foo) if(is(T == shared) && is(U ==
>> shared(Foo)*) || !is(T == shared) && is(U == Foo*)) {
>>          static if(is(T == shared)) _isShared = true;
>>          _foo = foo;
>>      }
>>
>>      ~this() {
>>          import std.stdio: writeln;
>>          _isShared ? writeln("shared dtor") : writeln("non-shared
>> dtor");
>>      }
>> }
>>
>> void main() {
>>      auto f = Foo();
>>      auto sf = shared Foo();
>>      auto s = S(&f);
>>      auto ss = shared S(&sf);
>> }
>>
>>
>> It's annoying to use that bool up memory-wise, but I assume it's not a big deal for most applications.
>>
>> In any case, that example wouldn't have worked anyway before my change to dmd - even creating the S struct would've been a compiler error.
>
> The problem with this is that this means that shared is not being properly enforced by the compiler. Your workaround is a way for the programmer to figure out if the object is shared and do something differently based on that, but for the compiler to do what it's supposed to be doing with shared (e.g. prevent non-atomic operations), any indirections in the member variables must continue to be typed as shared inside the destructor, and that's clearly not happening right now, which is a serious problem IMHO. The situation may be better thanks to your changes in that some stuff is now possible that should be possible and was not before, but it's not completely sound as far as the type system goes, and we really should be fixing it so that shared is properly enforced rather than just blindly stripped off.
>
> - Jonathan M Davis

I agree that this could be a problem, and that the proper solution is probably to allow the user to define more than one destructor. The problem isn't just with shared - immutable is similar, since you'd be able to invoke undefined behaviour from the destructor since immutable would be cast away and the compiler wouldn't even warn you. And that was already the behaviour in dmd. I think the situation isn't ideal but better than before.

I also think that while the problem exists, I don't think it'll be common. This would only affect structs that can be shared _and_ non-shared (or immutable).

This will require a DIP, methinks.

Atila

Atila
July 25, 2017
Am Thu, 20 Jul 2017 08:56:57 +0000
schrieb Kagamin <spam@here.lot>:

> On Wednesday, 19 July 2017 at 12:56:38 UTC, Marco Leise wrote:
> > That's exactly what I was opposing in the other post. These handles are opaque and never change their value. Within the Dlang language barrier they can be immutable and as such, implicitly shared.
> 
> Given transitivity of immutability the handle should have the same immutability as the resource it represents.

I understand that you apply D keywords to C types in a
best fit fashion, to get some errors from the compiler when
you use them in the wrong context. It should work alright in
some APIs (maybe you can show me an example).

But since C does not have these qualifiers, one function may be used for shared and unshared resources or the library may even be compiled with or without thread-safety enabled and you have to query that at *runtime*, where you have no help from the type-system. So I believe, relying on the pattern will be frustrating at times as not general enough.

What I seek to achieve by slapping immutable on things like file descriptors and opaque types is the use as hash table keys. As the hashed part of hash table keys must not change, this approach enables us to use these types as keys and statically verify immutability, too.

Because opaque structs and integer handles are used in C APIs to *hide* the implementation details, the compiler is deliberately left blind. That FILE* could as well be an integer as far as transitivity of `immutable` goes. Nothing the compiler *can see of it* will ever change. And that's all the type system will ever care about really! Even if part of that hidden structure is actually returned mutably by some function, it is just an implementation detail. Whether you slap `immutable` on anything there is mostly cosmetic.

Now what does that mean for type checks in practice?

1) Looking at POSIX' C `fgetc()` function, the stream is a
   plain FILE*: int fgetc (FILE *stream).
   Since I/O is thread-safe[1], it should ideally be `shared`
   the way you put it. And the way I look at it, it should be
   `immutable` on *our* side of the language barrier. (I.e.
   Dlang wont be able to change its contents or see the
   contents change.)

2) You can look up items by file descriptors or FILE* in hash
   tables implementations with immutable keys.

So we can take this away:
* Making a struct opaque, is implicitly making it immutable,
  because it's contents cannot be modified nor read directly -
  the compiler cannot reason about its contents.
* Now we also have a layman's head-const, making resource
  pointers usable as immutable hash table keys.

As you can see my thinking revolves around the idea that hash table keys must be immutable and that stems from the idea that once hashed and sorted into a table, the hashed data must not change. There are other approaches and druntime's AAs simply allow mutable keys:

	void main() {
		struct S { int* i; }
		int a = 1, b = 2;
		uint[S] aa;
		aa[S(&a)] = 42;
		foreach(ref s_key; aa.byKey())
			// Allows "innocent" changes to the keys
			s_key.i = &b;
		foreach(ref s_key; aa.byKey())
			// AA doesn't find the key it just returned! Range violation.
			uint u = aa[s_key];
	}

-- 
Marco

July 28, 2017
On Tuesday, 25 July 2017 at 19:22:11 UTC, Marco Leise wrote:
> I understand that you apply D keywords to C types in a
> best fit fashion, to get some errors from the compiler when
> you use them in the wrong context. It should work alright in
> some APIs (maybe you can show me an example).

I currently only use value types, and they unfortunately implicitly convert to all qualifiers.

> But since C does not have these qualifiers, one function may be used for shared and unshared resources or the library may even be compiled with or without thread-safety enabled and you have to query that at *runtime*, where you have no help from the type-system. So I believe, relying on the pattern will be frustrating at times as not general enough.

This means you use custom multithreading approach to work with the resources, shared qualifier would reflect it.

> What I seek to achieve by slapping immutable on things like file descriptors and opaque types is the use as hash table keys.

Reference types could benefit from this too, I use it regularly in C#, so it's not exclusive to value types. You just fight hash table design here. If this use case is useful, you shouldn't fight with the library.

> As the hashed part of hash table keys must not change, this approach enables us to use these types as keys and statically verify immutability, too.
> Because opaque structs and integer handles are used in C APIs to *hide* the implementation details, the compiler is deliberately left blind.

Blind compiler can't verify anything: it's blind. And ftell and lseek clearly indicate that file stream is not immutable.

>    Since I/O is thread-safe[1], it should ideally be `shared`
>    the way you put it.

Indeed, but only C11 specifies this.
July 29, 2017
On Tuesday, 25 July 2017 at 19:22:11 UTC, Marco Leise wrote:
>    Since I/O is thread-safe[1], it should ideally be `shared`
>    the way you put it.

Also it sounds like a bad decision.

void f()
{
  printf("hello ");
  printf("world\n");
}

If you have shared stdout and this function runs in 2 threads, you will get a character soup
---
hello hello world
world
---

If you have a separate line buffered stdout for each thread, you will get whole lines in the output
---
hello world
hello world
---
And incur no locking.
August 12, 2017
On Friday, 21 July 2017 at 08:53:44 UTC, Atila Neves wrote:

>
> This works fine in dmd 2.075:
>
> struct A
> {
>     this(string a) {}
>     this(string a) shared {}
>
>     ~this() {}
>
>     this(this T)(this) {} // you can reflect to find out if shared
> }
>
> void main()
> {
>     auto nonShared = A("");
>     auto shared_ = shared A("");
>     auto nonSharedCopy = nonShared;
>     auto sharedCopy = shared_;
> }
>
>
> Atila

This look interesting: this(this T)(this) {}
What is it? Postblit?

It compiles but doesn't work for me.

this(this T)(this) {writeln("postblit"); }   // doesn't print anything

Arek
August 14, 2017
On Saturday, 12 August 2017 at 19:34:35 UTC, Arek wrote:
> On Friday, 21 July 2017 at 08:53:44 UTC, Atila Neves wrote:
>
>>
>> This works fine in dmd 2.075:
>>
>> struct A
>> {
>>     this(string a) {}
>>     this(string a) shared {}
>>
>>     ~this() {}
>>
>>     this(this T)(this) {} // you can reflect to find out if shared
>> }
>>
>> void main()
>> {
>>     auto nonShared = A("");
>>     auto shared_ = shared A("");
>>     auto nonSharedCopy = nonShared;
>>     auto sharedCopy = shared_;
>> }
>>
>>
>> Atila
>
> This look interesting: this(this T)(this) {}
> What is it? Postblit?
>
> It compiles but doesn't work for me.
>
> this(this T)(this) {writeln("postblit"); }   // doesn't print anything
>
> Arek

It's a template postblit constructor - it'd get instantiated differently depending on the type of the implicit `this` parameter and would be able to fix things up taking into account whether or not `this` was shared (or immutable).

Atila
August 14, 2017
On Monday, 14 August 2017 at 16:49:22 UTC, Atila Neves wrote:
> On Saturday, 12 August 2017 at 19:34:35 UTC, Arek wrote:
>> On Friday, 21 July 2017 at 08:53:44 UTC, Atila Neves wrote:
... /cut/...
>
> It's a template postblit constructor - it'd get instantiated differently depending on the type of the implicit `this` parameter and would be able to fix things up taking into account whether or not `this` was shared (or immutable).
>
> Atila

I've tested this code on dmd 2.075.0 and it doesn't behave like postblit.

It's not executed in these statements:
    auto nonSharedCopy = nonShared;
    auto sharedCopy = shared_;

To get it executed, you have to change the statements into
    auto nonSharedCopy = A(nonShared);
    auto sharedCopy = A(shared_);

this(this T)(this) is compiled into

0000000000000000 <shared(ref shared(b.A) function(immutable(char)[])) b.A.__ctor>:
   0:	55                   	push   %rbp
   1:	48 8b ec             	mov    %rsp,%rbp
   4:	48 89 f8             	mov    %rdi,%rax
   7:	5d                   	pop    %rbp
   8:	c3                   	retq
   9:	00 00                	add    %al,(%rax)
	...
what is exacly the same as the constructor:

0000000000000000 <ref b.A b.A.__ctor(immutable(char)[])>:
   0:	55                   	push   %rbp
   1:	48 8b ec             	mov    %rsp,%rbp
   4:	48 89 f8             	mov    %rdi,%rax
   7:	5d                   	pop    %rbp
   8:	c3                   	retq
   9:	00 00                	add    %al,(%rax)
	...

The real postblit looks like this:
Disassembly of section .text.void b.A.__postblit():

0000000000000000 <void b.A.__postblit()>:
   0:	55                   	push   %rbp
   1:	48 8b ec             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
   c:	b8 73 00 00 00       	mov    $0x73,%eax
  11:	b9 0a 00 00 00       	mov    $0xa,%ecx
  16:	99                   	cltd
  17:	f7 f9                	idiv   %ecx
  19:	48 83 7d f8 00       	cmpq   $0x0,-0x8(%rbp)
  1e:	75 2e                	jne    4e <void b.A.__postblit()+0x4e>
  20:	49 89 c8             	mov    %rcx,%r8
  23:	48 8d 0d 00 00 00 00 	lea    0x0(%rip),%rcx        # 2a <void b.A.__postblit()+0x2a>
  2a:	b8 03 00 00 00       	mov    $0x3,%eax
  2f:	48 89 c2             	mov    %rax,%rdx
  32:	48 89 55 f0          	mov    %rdx,-0x10(%rbp)
  36:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # 3d <void b.A.__postblit()+0x3d>
  3d:	bf 09 00 00 00       	mov    $0x9,%edi
  42:	48 89 d6             	mov    %rdx,%rsi
  45:	48 8b 55 f0          	mov    -0x10(%rbp),%rdx
  49:	e8 00 00 00 00       	callq  4e <void b.A.__postblit()+0x4e>
  4e:	c9                   	leaveq
  4f:	c3                   	retq

So, in my opinion, your example compiles (but doesn't work) because it doesn't have the user defined postblit.

It's a pity, because it's looked promising.

Arek
August 14, 2017
On Monday, 14 August 2017 at 16:49:22 UTC, Atila Neves wrote:
> On Saturday, 12 August 2017 at 19:34:35 UTC, Arek wrote:
>> On Friday, 21 July 2017 at 08:53:44 UTC, Atila Neves wrote:

>
> It's a template postblit constructor - it'd get instantiated differently depending on the type of the implicit `this` parameter and would be able to fix things up taking into account whether or not `this` was shared (or immutable).
>
> Atila

Sorry - my mistake.

This fancy template compiles into

Disassembly of section .text.ref b.A b.A.__ctor!(b.A).__ctor(b.A):

0000000000000000 <ref b.A b.A.__ctor!(b.A).__ctor(b.A)>:
   0:	55                   	push   %rbp
   1:	48 8b ec             	mov    %rsp,%rbp
   4:	48 83 ec 30          	sub    $0x30,%rsp
   8:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
   c:	48 8d 0d 00 00 00 00 	lea    0x0(%rip),%rcx        # 13 <ref b.A b.A.__ctor!(b.A).__ctor(b.A)+0x13>
  13:	b8 0a 00 00 00       	mov    $0xa,%eax
... cut...

But it's still not the postblit and doesn't works like postblit.

Arek
August 15, 2017
On Monday, 14 August 2017 at 18:59:17 UTC, Arek wrote:
> On Monday, 14 August 2017 at 16:49:22 UTC, Atila Neves wrote:
>>> [...]
> ... /cut/...
>> [...]
>
> I've tested this code on dmd 2.075.0 and it doesn't behave like postblit.
>
> [...]

I'd have to double check, but this seems like a bug to me.

Atila
1 2 3 4 5
Next ›   Last »