Jump to content
Mike Torrettinni

The Case of Delphi Const String Parameters

Recommended Posts

@Dalija Prasnikar I am sorry but i have to disagree, i read the article and i think i understand your point of view on all on this, but please let me explain how this is started as a bug and flaw in design in the RTL then been fixed by another bug in the compiler, and that fix was half solution and brought huge performance hit with it, in short it is compiler and RTL bug.

 

let me put this smallest example to explain 

uses
  System.SysUtils;

procedure WriteItDown(Msg: string);
begin
  Writeln(Msg);
end;

begin
  WriteItDown('123');
end.

No if you would like put a break point on the WriteItDown's begin and follow the assembly, here is the what would we have

image.thumb.png.31a7b1c5d9539616010cb838283b84b8.png

You see all these functions within WriteItDown are intrinsic functions means the compiler only decide where and when to put them, i can't care any less about WriteLn and why it is 3, it is irrelevant for our case here.

 

The point here are the following

1) UStrAddRef  ,the name is explanatory enough, but let me explain more to refresh information, it will make sure the content of the string in this case are protected against freeing by increasing the refence count of the content.

2) there is try..finally to make sure the counter part of the above (1) is been called.

3) UStrClr this will clear a string, means it will should decrease the reference count by one and see if it is 0 then simply free the memory.

 

Simple, right ?

In theory yes, but lets see how things get complicated more by flawed design, as you can see in the screenshot of assembly the string pointer is there and it is pointing to its value (content) before that there is the length (3 chars) and before that the value of -1 ($FFFFFFFF), because this is const value string, that is logical, now what is not logical and flawed is the lack of set of intrinsic functions that handle constants managed type !

To dig deeper try to step into UStrAddRef and you will see it check for nil pointer then check reference count against if the value is -1 in this case it will do nothing, and this is right, or we can call a rightful design not the best but it would/could work, also UStrClr will do similar thing it will see if the value of reference count against 1 but the value here is -1, so it will do nothing.

 

Now where is the bug ?! , It is in the answers of the following easy questions

1) Why try..finally block is needed along with reference count where the compiler can easily see it as const value to begin with ?

2) If the RTL is designed to skip handling constant values by checking against -1 then why the compiler is not doing so on managed type parameters declared as const .

3) Shouldn't the RTL have different set of these intrinsic functions to handle const parameters, as these belongs to the compiler alone to choose between, i mean come on, the compiler will not allow SetLength to be called on const parameters so why not to choose to skip all the above AddRef-try..finally-DecRef , can the compiler detect if the the parameters without const or var been used then add the mentioned protection.

4) Is the RTL designed initially to handle -1 as reference count for constants and then they forgot to adjust the compiler the the RTL for const-declared-runtime/dynamic-value ?

 

Now can you see that as bug over a bug with lot of unjustified inefficiency ?

By the way Java doesn't do that, in fact no compiler ever does that, it either fix the compiler or the RTL or both, also the change to make the compiler skip RefAdd and RefDec on const parameter is not huge deal, i am not sure what would be the impact do such thing now with the same team that working on the compiler and the RTL and refuse to see the flaw, also making the compiler fix the RefCount to -1 before the function or inside the function then restore it will fix this once and for all and it will be only a few instructions, but will introduce another unjustified overhead. 

 

 

Delphi compiler and RTL really need a review, and i hope that i explained how this is a bug and flawed design, also really sorry for the English (i tried!)

Share this post


Link to post
21 minutes ago, Kas Ob. said:

Delphi compiler and RTL really need a review, and i hope that i explained how this is a bug and flawed design, also really sorry for the English (i tried!)

You are mixing a lot of things here. 

 

First, Java has garbage collection, so you absolutely cannot compare it to what Delphi does.

 

WriteItDown procedure is compiled independently of the call site - it has to cover all possible variants. So while your example only passes string literal that has special handling, WriteItDown still has to take care of situation where dynamically allocated string variable will be passed to it. Hence try...finally block. 

 

As for the rest is concerned I am not sure I am following what you want to say... what const are you talking about? WriteItDown does not have const parameter, so it increases and decreases reference count as expected. 

 

Again there is no flaw in compiler, is works exactly as intended. If you pass reference counted parameter as value it will trigger reference counting mechanism, if you pass it as const it will not. You can choose to make parameter const or not depending on what method does with it. And again special handling for string literals is required because compiler does not know what kind of string you passed in when you called the method. 

Share this post


Link to post
Posted (edited)

There is a well known design flaw with passing arguments to const arguments.

procedure Foo(const Bar: IMyInterface);
....
Foo(TMyImplementingClass.Create);

As we know, Foo won't increment the reference count because Bar is const. Possible issues are the object is leaked, or released early inside Foo (e.g. if something inside takes a reference and then releases it).

 

This should be fixed by having the compiler realise at the call site that something needs to take a reference to the interface while the function is executing, and emitting the necessary code at the call site. I've given up hope that this will ever be fixed.

 

On the other hand, perhaps I should be pleased that Idera consider itself to be one of the hottest companies around.

Edited by David Heffernan
  • Like 1

Share this post


Link to post
7 minutes ago, Dalija Prasnikar said:

what const are you talking about? WriteItDown does not have const parameter, so it increases and decreases reference count as expected. 

Then why literal strings compiler with RefCount equal to -1, this is not zero and neither a positive value, my point is to find how the RTL designed and built to handle literal, which in case of const parameter should behave the same

10 minutes ago, Dalija Prasnikar said:

so it increases and decreases reference count as expected. 

It should only when the parameter had been used within the function, right ? and by used i mean assigned or cloned, it just been passed along then let the receiver worry about that as long this scope is not assigning from/to it.

but the compiler should also know what is what and where, and it failed here with one function with default string parameter with (const or var).

The compiler literally looks like adding these add-try-dec everywhere out of sport.

 

11 minutes ago, Dalija Prasnikar said:

Again there is no flaw in compiler

It is flawed is by assuming the RTL is built to handle managed type the same way const declared or not, and that is wrong assumption, the proof

1) consts are expected to have -1 as reference count by the RTL, and i think i already made that clear by now.

2) there is no way to get -1 as reference count by generated code without low level memory manipulation, the compiler is not ensuring -1 for a const parameter.

3) lets assume the compiler is right, then the RTL is lacking the intrinsic function to handle const values, even if they were introduced then it is compiler's responsibility to choose the right one.

 

I made my example above as simple as i can to prove these points, and made it with only string without (const or var) to show the flawed design that has been fixed by wrong compiler behaviour.

 

Leave try and finally for now and focus on how the RTL and intrinsic built and what they do expect as reference count to be logically right, it is -1 which the compiler is not enforcing, would this be enough argument for buggy compiler ( and most likely the RTL too) ?

Share this post


Link to post
46 minutes ago, Kas Ob. said:

It is flawed is by assuming the RTL is built to handle managed type the same way const declared or not, and that is wrong assumption, the proof

1) consts are expected to have -1 as reference count by the RTL, and i think i already made that clear by now.

2) there is no way to get -1 as reference count by generated code without low level memory manipulation, the compiler is not ensuring -1 for a const parameter.

3) lets assume the compiler is right, then the RTL is lacking the intrinsic function to handle const values, even if they were introduced then it is compiler's responsibility to choose the right one.

This makes no sense whatsoever.

  • Like 2

Share this post


Link to post
52 minutes ago, David Heffernan said:

There is a well known design flaw with passing arguments to const arguments.

I probably wasn't specific enough when I said there are no flaws in compiler. I meant only related to what @Kas Ob. was saying and his example does not have const parameters. Also, only object instances suffer from this particular bug.

 

Share this post


Link to post
Posted (edited)

We should try to split concepts from implementation details.

 

Conceptually "const" means that we are not allowed to change the value of the parameter inside the function. Conceptually "const" does not necessary imply passing by reference. Here is a definition from docs:

 

Quote

A constant (const) parameter is like a local constant or read-only variable. Constant parameters are similar to value parameters, except that you cannot assign a value to a constant parameter within the body of a procedure or function...

 

Passing by reference can be considered only as an optimization, which compiler should do silently and on its own risk. Optimizations should not violate formal language concepts and should not introduce side effects in correct code.

 

Original Marco's code is fully correct, and should work, simply because Value parameter is not assigned inside the function body, and thus "const" constraint is not violated by the programmer.

procedure TForm1.Button1Click(Sender: TObject);
var
  s1: string;

  procedure Test(const Value: string);
  begin
    s1 := '456';
    ShowMessage(Value);
  end;

begin
  s1 := Copy('123', 1);
  Test(s1);
end;

* All this is my theoretical viewpoint.

 

Edited by balabuev

Share this post


Link to post
12 minutes ago, balabuev said:

Original Marco's code is fully correct, and should work, simply because Value parameter is not assigned inside the function body, and thus "const" constraint is not violated by the programmer.

Not sure if this can be sorted as "not violated" or simply a "bug" introduced by the programmer, but what make it worse is the ignorance of the compiler to produce code protecting against this.

 

It is not easy to find solution, but to do so first we must classify this as a bug and design flaw (shortsighted), only after that we can think about a solution, and right of the bat i can think of a fix or solution for the compiler and RTL.

 

See, strings and interfaces are slightly different beast internally hence the solution should be little different, and what i am thinking of is this, what if the compiler negated the refcount before a call and negated it back after return, the called function with const parameter should use different or little tweaked RTL internal functions (intrinsic) in case with interfaces it will raise exception when refcount is negative thus will pinpoint such cases right away at first run of the code, for strings it will use alternative functions to skip freeing the string.

And i know about the performance hit with such modification, but what should prefer speed or consistency ?

 

That was one, now what we can retrieve from performance to cover the hit from the suggested solution is rethink how strings is refcounted, because it is way overdone, consider this code

procedure C(st: string);
begin
  Writeln(st);
end;

procedure B(st: string);
begin
  C(st);
end;

procedure A(st: string);
begin
  B(st);
end;

in C we have string has refcount at +3 from initially value at begin of A, why ? ... what could possibly have changed in lets say B to increase the refcount, if the value to be shared then lets C do it, in C we didn't change anything then remove the refcount, in WriteLn do the refcount only if needed,

In other words do we have thread safe strings ? No, then why the compiler is sticking and overdoing an unproductive and useless pattern with no benefit at all, when the execution path that the compiler is generated machine code for is clear as rain.

Share this post


Link to post
16 hours ago, balabuev said:

We should try to split concepts from implementation details.

In theory concepts are one thing, compiler specifications and implementation details another. Unfortunately Delphi does not have formal compiler specification, and compiles implementation is often the only specification. Of course, there are always corner cases where it is hard to say whether some behavior is intended or it is a bug, but in this case - const parameters behaviors are pretty well defined.

16 hours ago, balabuev said:

Conceptually "const" means that we are not allowed to change the value of the parameter inside the function. Conceptually "const" does not necessary imply passing by reference. Here is a definition from docs:

 

Passing by reference can be considered only as an optimization, which compiler should do silently and on its own risk. Optimizations should not violate formal language concepts and should not introduce side effects in correct code.

 

I don't think you fully grasp meaning of the above sentence. Yes, const means that you are not allowed to change parameter inside function and it does not necessarily imply passing by reference. But, you are forgetting that reference types consist of two parts - reference (that is the only part passed as parameter and cannot be changed) and associated content (data) that is allocated on a heap. 

 

Besides reference types, Delphi also has value types. And the optimization part of passing by reference is primarily for passing value types larger than register size.  Since references (pointers) are not larger than register size, they will not be passed by reference, but by value. 

 

 

16 hours ago, balabuev said:

Original Marco's code is fully correct, and should work, simply because Value parameter is not assigned inside the function body, and thus "const" constraint is not violated by the programmer.


procedure TForm1.Button1Click(Sender: TObject);
var
  s1: string;

  procedure Test(const Value: string);
  begin
    s1 := '456';
    ShowMessage(Value);
  end;

begin
  s1 := Copy('123', 1);
  Test(s1);
end;

* All this is my theoretical viewpoint.

 

 

Code is not correct and it will fail, not because Value is somehow changed - it is not and it is fully compliant with definition of const parameters. Problem arises because being passed as const - where reference cannot be changed triggers reference counting optimization for reference counted types. Since parameter will not be changed, there is not need to take care of reference counting. Because reference counting is not triggered, original string (same would apply for any other reference counted type) where Value reference points to, will be destroyed when s1 is assigned new value - in that moment Value reference that was not changed will point to now invalid memory location. 

 

It is also important to note that actual reference count is maintained as part of heap allocated data - not inside reference variable.

 

Share this post


Link to post
16 hours ago, Kas Ob. said:

in C we have string has refcount at +3 from initially value at begin of A, why ? ... what could possibly have changed in lets say B to increase the refcount, if the value to be shared then lets C do it, in C we didn't change anything then remove the refcount, in WriteLn do the refcount only if needed,

In other words do we have thread safe strings ? No, then why the compiler is sticking and overdoing an unproductive and useless pattern with no benefit at all, when the execution path that the compiler is generated machine code for is clear as rain.

Your posts clearly show that you have no idea how reference counting actually works, nor how compiler works. I don't mean this as insult, but merely as observation.  Not knowing something is not a problem on its own, but it is hard to have meaningful discussion when other party does not have basic idea about discussed topics. 

 

I could try to explain it in more details, but I don't know from where should I start. Especially, because you seem to have wrong perception about how compiler works. I am worried that we would be running in circles. Also both topics have enough content around that covering everything from the start would require writing a book.

  • Like 1

Share this post


Link to post
Posted (edited)

I want to say that you again messed up concepts and implementation details. Pointers, heap data, ref-counts - are all implementation details terms. And just for note, I undestand how things works internally very precisely. But, I intentionaly want to keep the logic on the conceptual level.

 

41 minutes ago, Dalija Prasnikar said:

But, you are forgetting that reference types consist of two parts - reference (that is the only part passed as parameter and cannot be changed) and associated content (data) that is allocated on a heap

 

[1] Well, yes and no. Being formal - you are not correct, because the value of reference type is the reference itself. Yes it points to some associated data, but the value is just a reference itself.

 

[2] Conceptually, string type in Delphi is a value type (I don't care about implementation details).

 

[3] As described in documentation, "const" parameters are variables, just like value parameters. And this is quite important, because we should recall simple thing: variable is a slot which holds a value.

 

From [1], [2] and [3] the following very important idea can be formulated:

 

Because of [3] each variable holds its own value. Values (conceptually) are not shared between two or more variables. Because of [1] this is also true for reference types. And, finally repeating [2] - string type in Delphi is a value type, NOT a reference type.

 

* Disclaimer: As I noted in this topic previously, practically, I agree that "const" modifiers cannot be implemented better than currently. And so, this is a kind of unfixable inconsistency.

 

 

 

Edited by balabuev

Share this post


Link to post
Posted (edited)
30 minutes ago, balabuev said:

I want to say that you again messed up concepts and implementation details. Pointers, heap data, ref-counts - are all implementation details terms. And just for note, I undestand how things works internally very precisely. But, I intentionaly want to keep the logic on the conceptual level.

 

Compilers (languages) are always leaky abstractions. At some level you have to define what specific concept means and then that leads to its implementation. There is no universal concept of const parameters. There are many ways how parameters are passed and different languages greatly differ.

 

Quote

[1] Well, yes and no. Being formal - you are not correct, because the value of reference type is the reference itself. Yes it points to some associated heap data, but the value is just a reference itself.

 

[2] Conceptually, string type in Delphi is a value type (I don't care about implementation details).

 

[3] As described in documentation, "const" parameters are variables, just like value parameters. And this is quite important, because we should recall simple thing: variable is a slot which holds a value.

 

From [1], [2] and [3] the following very important idea can be formulated:

 

Because of [3] each variable holds its own value. Values (conceptually) are not shared between two or more variables. Because of [1] this is also true for reference types. And, finally repeating [2] - string type in Delphi is a value type, NOT a reference type.

 

* Disclaimer: As I noted in this topic previously, practically, I agree that "const" modifiers cannot be implemented better than currently. And so, this is a kind of unfixable inconsistency.

 

 

String type is reference type, not a value type. Only short strings are value types. There is no conceptual here, at least not when Delphi is concerned. Your conceptual discussion would be discussing some completely different language.

Edited by Dalija Prasnikar

Share this post


Link to post
3 minutes ago, Dalija Prasnikar said:

String type is reference type, not a value type.

String type conceptually is a value type. Come on! All thouse tricks, like ARC and copy-on-write (as well as copy on taking address of char, etc) are exist to allow string type to be a value type.

 

 

Share this post


Link to post
1 hour ago, balabuev said:

I agree that "const" modifiers cannot be implemented better than currently. And so, this is a kind of unfixable inconsistency.

Would you please entertain the idea of fix a hypothetical fix ?

 

Lets take Interfaces this time for Dalija preference, so we have IntfCopy and IntfClear are the ones responsible to manage and call AddRef and Release, also lets imagine a caller function negated the reference count for const interface parameter before the calling and here we didn't lose the count we just marked it negative to signal it as protected, now we can have IntfCopy and IntfClear to raise an exception, this can be build as assertion in debug build and this behaviour removed form the release if that is not OK, but i think it can thought to work, the idea is to use the signal bit in RefCount as boolean to protected the value form freeing or overwriting, the same principle can work with strings, the compiler will be the one responsible to flick that bit before passing a variable as const and flick it back.

 

Can this be a seed for a fix ? or just extra check just like overflow and overrun checks ? or am i just lost in stupidity ?

Share this post


Link to post
Posted (edited)
6 minutes ago, Kas Ob. said:

negated the reference count for const interface parameter

Negating the reference count is not better than just incrementing it. Because you still have to do:

 

- Slow interlocking.

- Negating it back at the end, which will still imply try/finally.

 

And so, there no sense to introduce another layer of complexity.

 

Edited by balabuev
  • Like 1

Share this post


Link to post
26 minutes ago, balabuev said:

String type conceptually is a value type. Come on! All thouse tricks, like ARC and copy-on-write (as well as copy on taking address of char, etc) are exist to allow string type to be a value type.

Copy on write is not equal to being value type. You may not like "implementation details" and "tricks" but they are more than implementation details, they achieve desired behaviors. You cannot have full value type behavior with something that is not value type. You can only simulate some parts, but that also means that implementation details of actually being a reference type and having ARC to handle its lifetime and COW leak through.

Share this post


Link to post
3 minutes ago, Dalija Prasnikar said:

Copy on write is not equal to being value type. You may not like "implementation details" and "tricks" but they are more than implementation details, they achieve desired behaviors.

The "desired behaviors" of the string type is to behave like a value type.

 

Anyway, "s1" and "Value" in the test code are two different variables. Both of them store values, so we speak about two different (but equal) string values. And when you change one of them, the other should not be changed. Because:

 

- [My viewpoint] string is a value type.

- [Your viewpoint] internally string implements COW, and so, this is a desired behavior.

 

22 minutes ago, Dalija Prasnikar said:

a reference type and having ARC to handle its lifetime and COW leak through.

When something is leaking through - it's called a bug.

 

Share this post


Link to post
10 minutes ago, balabuev said:

When something is leaking through - it's called a bug.

Then why the suggested behaviour above make no sense, based on the overhead causing slowness and complexity, while the overhead from integer overflow does make sense ?

Share this post


Link to post
5 minutes ago, balabuev said:

The "desired behaviors" of the string type is to behave like a value type.

Emphasis is on BEHAVE LIKE. It is not string IS value type. And simulating something is not equal to having that exact thing.

 

5 minutes ago, balabuev said:

Anyway, "s1" and "Value" in the test code are two different variables. Both of them store values, so we speak about two different (but equal) string values. And when you change one of them, the other should not be changed. Because:

 

- [My viewpoint] string is a value type.

- [Your viewpoint] internally string implements COW, and so, this is a desired behavior.

string in Delphi is reference type. It is not value type. This is not a viewpoint it is a fact. If you cannot acknowledge facts, this discussion is pointless.

 

s1 and Value are two different references to ONE memory location. When data in that memory location is destroyed because you change s1 variable that was only strong reference to that memory location, Value will point to invalid memory. 

 

5 minutes ago, balabuev said:

When something is leaking through - it's called a bug.

 

I am talking about leaking abstractions (as implementation details cannot be completely hidden), not bugs. You cannot completely separate idea (concept) of a string and implementation of a string. 

 

Share this post


Link to post
4 minutes ago, Dalija Prasnikar said:

string in Delphi is reference type. It is not value type. This is not a viewpoint it is a fact. If you cannot acknowledge facts, this discussion is pointless.

You invented this definition for youself. Btw, in fact, the definitions of "value type" and "reference type" are nowhere documented. And I do not agree to equalize reference type notion with just something allocated on heap.

 

In common sense reference type is something, which allow to hold references to shared data (emphasis, as you say, is on SHARED word). This "shared" property should be visible to the user. So, the user should be able to determine, whether two references are pointing to single data or not.

 

String data does not have this property, because given two string variables:

 

- On read operartions you cannot really see the difference between phisically shared chars (single memory block) or two different memory blocks with the same content.

- On write (or take address of) operations strings utilizes COW, and so, no writing to shared data really happened.

 

Of course, you can determine this using some hack like: 

if Pointer(s1) = Pointer(s2) then

But, thats another story.

 

9 minutes ago, Dalija Prasnikar said:

I am talking about leaking abstractions

 

Sure. I understand. But, this does not change my point.

Share this post


Link to post
1 hour ago, balabuev said:

You invented this definition for youself. Btw, in fact, the definitions of "value type" and "reference type" are nowhere documented. And I do not agree to equalize reference type notion with just something allocated on heap.

 

Sure... yep... this is my imagination running wild... https://en.wikipedia.org/wiki/Value_type_and_reference_type

 

1 hour ago, balabuev said:

In common sense reference type is something, which allow to hold references to shared data (emphasis, as you say, is on SHARED word). This "shared" property should be visible to the user. So, the user should be able to determine, whether two references are pointing to single data or not.

 

There is no common sense there is clear definition what reference type is and what value type is. 

 

You can always see address stored in any reference type, including string. It just requires typecast.

 

  • Like 1

Share this post


Link to post
Posted (edited)
17 minutes ago, Dalija Prasnikar said:

Sure... yep... this is my imagination running wild

I meant in Delphi's documentation. You yourself suspected me that I'm speaking about anything, but not about Delphi, right?

 

Quote

In computer programming, data types can be divided into two categories: value types and reference types. A value of value type is the actual value. A value of reference type is a reference to another value.

But, even this definition from Wikipedia, which is by the way quite abstract (as it should be!), does not contradict with my point of view. Value of the string type is an ordered sequence of characters. Logically, nothing wrong.

 

 

Edited by balabuev

Share this post


Link to post
16 hours ago, balabuev said:

I meant in Delphi's documentation. You yourself suspected me that I'm speaking about anything, but not about Delphi, right?

 

Programming languages share some common concepts. But it is important to distinguish things that are comparable and things that are not comparable. Definition of value and reference type does not change with language. There are various implementation details around both, but basic classification is the same. Value types are stored directly in memory associated by variable. And when you are accessing that memory you will always access valid memory as long as you can access that variable. Reference types have two parts, and variable only holds pointer to the actual data that is dynamically allocated on heap. That associated memory holding actual data, may or may not be allocated and your variable can point at invalid memory location that does not hold valid data.

 

That crucial difference between value types and reference types also has huge impact on their behavior in any language. But the actual behavior will differ because every language has different memory management. So what happens and how memory of associated reference is handled cannot be easily compared - this is where you cannot say, Java does this... so Delphi should be able to do this too.  

 

And I said "Java has garbage collection, so you absolutely cannot compare it to what Delphi does."

 

The fact that Delphi documentation does not specifically mentions value types and reference types that does not mean that this classification does not exist and that it does not have impact on behavior of different data types. Many things are not explicitly written in documentation, that does not mean they are not real.

 

From http://docwiki.embarcadero.com/RADStudio/Sydney/en/Internal_Data_Formats_(Delphi) description of string type it is pretty clear that string variable holds pointer to actual data, and that is clear definition of reference type.

 

Quote

A string variable of type UnicodeString or AnsiString occupies 4 bytes of memory on 32-bit platforms (and 8 bytes on 64-bit) that contain a pointer to a dynamically allocated string. When a string variable is empty (contains a zero-length string), the string pointer is nil and no dynamic memory is associated with the string variable. For a nonempty string value, the string pointer points to a dynamically allocated block of memory that contains the string value in addition to information describing the string.

 

16 hours ago, balabuev said:

But, even this definition from Wikipedia, which is by the way quite abstract (as it should be!), does not contradict with my point of view. Value of the string type is an ordered sequence of characters. Logically, nothing wrong

 

You are confusing value of the string with value type. Value of the string is sequence of characters, but they are not directly stored in string variable, but in some other location. Value stored in the string variable itself is just pointer to that location.

Share this post


Link to post
20 hours ago, Kas Ob. said:

Would you please entertain the idea of fix a hypothetical fix ?

 

Lets take Interfaces this time for Dalija preference, so we have IntfCopy and IntfClear are the ones responsible to manage and call AddRef and Release, also lets imagine a caller function negated the reference count for const interface parameter before the calling and here we didn't lose the count we just marked it negative to signal it as protected, now we can have IntfCopy and IntfClear to raise an exception, this can be build as assertion in debug build and this behaviour removed form the release if that is not OK, but i think it can thought to work, the idea is to use the signal bit in RefCount as boolean to protected the value form freeing or overwriting, the same principle can work with strings, the compiler will be the one responsible to flick that bit before passing a variable as const and flick it back.

 

Can this be a seed for a fix ? or just extra check just like overflow and overrun checks ? or am i just lost in stupidity ?

 

It is called reference counting not bit flipping for a reason.  There is no negative reference count. Any reference counted instance is destroyed when reference count reaches zero. 

 

Point of reference counting is that reference count is increased a points where instance (string, object, ...) gets additional reference - if you assign it to another variable, but also when you pass it as parameter. Most of the time passing parameters does not require increasing reference count, but compiler is not capable of analyzing whether this is required or not. This is why programmer must decide whether or not reference count trigger is required for some parameter or not, depending on the code within the procedure. If you want to skip unnecessary reference counting then you should mark parameter as const, if you need reference counting trigger then you don't. 

 

The only possible fix (the one Marco implied when he said that compiler will not change) would be that const parameter ALWAYS trigger reference counting. Since whole point of const when managed types are concerned is to prevent unnecessary reference counting and performance penalty, obviously that kind of fix would make no sense at all.

Share this post


Link to post
19 minutes ago, Dalija Prasnikar said:

It is called reference counting not bit flipping for a reason.  There is no negative reference count. Any reference counted instance is destroyed when reference count reaches zero. 

Lets establish few things and facts first

1) We increase the negative value away from the 0 means increase a positive value by adding +1, and increase the flagged (hence negative value) by adding -1 (aka subtracting), here the more i think about it the more it become clear the algorithm to do this is so simple and in fact one line in decent and well designed language, in C it will be this

Quote

RefCount += (RefCount >> 31) | 1;

which translate in Delphi to 

if RefCount >= 0 then
  RefCount := RefCount+1 else
  RefCount := RefCount-1;

In Delphi we can't use the same simple bit shifting because the compiler and the language for ever ignored the fact the shr functionality is broken and doesn't differentiate between signed and unsigned integers,

And in assembly this will be branchless and fast something like this

mov     eax, edi
sar     eax, 31
or      eax, 1
add     eax, edi

 The same algorithm can be used to increase a positive value or decrease negative value.

 

2) By depending on the sign bit we removed the need to introduce another field, something to mark the content as protected and that is the whole point, and yet by negating the RefCount we didn't broke current functionality, and we didn't introduce broken behaviour.

 

3) The compiler is protecting the logical functionality for simple types and any type that should only be the compiler responsibility to generate the code for and guarantee the language integrity, just like integers and their overflow checks.

 

Now to explain how this will work, and lets take my other example the one with A calling B calling C, and lets assume the B is declared with const string while A and C without, the current behaviour will introduce the RefCount Addition on A and C while skip B,

The suggested behaviour require A to flip the sign bit ( aka mark the content as protected and here i can't repeat this enough it is irrelevant what type it does protect/hold, as long the RefCount there then it is holding/protecting something)

A will call B, we don't have changed B in it and the RefCount is still be -1 coming from A, while B will call C, and here C will not change anything in code from current implementation and we still have the RefCount at -2, because we used the suggest algorithm above, hence we didn't break the RefCount usage, also the code for decreasing the RefCount will use the same algorithm means for a positive value it will decrease and for negative value it will increase ( add +1 because it is negative) 

When the code execution returned to A it will flip the sign again and mark the content unprotected again and continue the flow as normal, why all of this ?

 

Lets imagine what would be the behavour with the mentioned behaviour that cause leaks(lose data, freeing still needed data..) when Value as const (in other example with strings and s1) been passed it will have the protection bit flagged means all the corresponding intrinsic functions like ( IntfCopy, IntfClear, UStrAsg....) should allow reaching 0 from negative value and should raise an exception or copy the value ( or the content .. what every you want to call it as it is what RefCount protecting), when the code will execure s1 assigning it will either copy Value to local or raise an exception, the difference here between these two cases is what we can discuss instead of marking each other ignorant.

 

Can this be better approach from give up and say it is what it is, or acknowledge it as bug and short step from the compiler and RTL should be fixed, although even if it is not practical and it can be used in debug as an extra check just like integer overflow and will work fine, prevent hidden bugs that can easily be detected by a test instead waiting it to happen in production.

 

What is perplexing me is your insistence that this can't be fixed or even merely the idea it can be fix is a blasphemy,  why ?! technology and science didn't evolve by refusing new ideas crazy or not.

By the way, the way you start to explain why it can't work is way better (and respectful from you) than just mark me as stupid, ignorant or don't understand a thing, while you didn't even try to understand the idea or asked questions to clarify things that most likely i didn't explain them right.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×