Recent

Author Topic: Request for peer review: classes on the stack  (Read 2096 times)

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Request for peer review: classes on the stack
« on: August 20, 2018, 12:18:35 pm »
This is a follow up on my reply from this thread: http://forum.lazarus.freepascal.org/index.php/topic,42281.msg294895.html#msg294895
I succeeded in writing code (for now for win64 only)...that can actually allocate class instances on the stack. There are quite some limitations, but it works.
To examine the code for windows 64  download this: http://forum.lazarus.freepascal.org/index.php?action=dlattach;topic=42281.0;attach=27945
And use this unit here: http://forum.lazarus.freepascal.org/index.php/topic,42281.msg294895.html#msg294895

A very simple example looks like this:
Code: Pascal  [Select][+][-]
  1. {$mode delphi}{$S+}{$H-}
  2. uses ClassOnStack;
  3. type
  4.   TMyStackObject = class(TStackObject)
  5.   public
  6.     procedure Show;
  7.   end;
  8.   procedure TMyStackObject.Show;
  9.   begin
  10.     writeln('Stack based class instance');
  11.   end;
  12.  
  13. begin
  14.   with TMyStackObject.Create do show; // no need to call free;
  15. end.
Compile with fpc -glh -gt -Ct
Confirm there is no heap allocation and the stack is not corrupted....({$S+} is specified and the heap manager is used )

Note you *must* follow the rules as defined in ClassOnStack.pas. Basically no managed types...
A nice feature is that the class instance vmt is also on the stack, so virtual and override also work.

Within the defined limitations: please shoot holes in it. And mind your stack size!!


 
« Last Edit: August 20, 2018, 12:38:02 pm by Thaddy »
Specialize a type, not a var.

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Request for peer review: classes on the stack
« Reply #1 on: August 20, 2018, 03:39:40 pm »
I found a small issue myself with parameterless constructors. These work except for the first level derived class. Strange because the base create does nothing.
I will investigate if I should document it as a limitation or that there is a solution. Subsequent second level derived parameterless constructors work as expected.

A.t.m. it only works without optimization, but I know how to solve that. I edited the unit to compile in {$O-} state.
There will be a new project attachment with tests later this evening.
« Last Edit: August 20, 2018, 05:12:46 pm by Thaddy »
Specialize a type, not a var.

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Request for peer review: classes on the stack
« Reply #2 on: August 20, 2018, 08:55:50 pm »
New version, see attachment.
- improved
- simplified
- better demo
- optimizations now work
« Last Edit: August 20, 2018, 09:18:52 pm by Thaddy »
Specialize a type, not a var.

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Request for peer review: classes on the stack
« Reply #3 on: August 20, 2018, 09:55:38 pm »
Take a look at this thread.

Thaddy

  • Hero Member
  • *****
  • Posts: 14201
  • Probably until I exterminate Putin.
Re: Request for peer review: classes on the stack
« Reply #4 on: August 20, 2018, 10:03:16 pm »
@engkin
I know that thread. Case in point is that I think this solution is more elegant (and simpler) because it hides the complexities and does not need to call external routines.
I see if I can add a 32 bit version based on that code.
As long as you derive from TStackObject everything else is transparent. It is not for use with existing classes.
(Although existing classes that satisfy the constraints can simply change root ancestor from TObject to TStackObject)
« Last Edit: August 20, 2018, 10:09:54 pm by Thaddy »
Specialize a type, not a var.

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: Request for peer review: classes on the stack
« Reply #5 on: July 12, 2023, 10:28:39 am »
I know this topic is a bit old, but it has been linked to quite often recently, and when I was looking at that I noticed that this could not really work.

So I did a small test:
Code: Pascal  [Select][+][-]
  1. procedure Smack;
  2. var
  3.   arr: array[0..1023] of Byte;
  4. begin
  5.   FillChar(arr, SizeOf(arr), $FF);
  6. end;
  7.  
  8. procedure TestStackObject;
  9. var
  10.   s: TStackObject;
  11. begin
  12.   s := TStackObject.Create;
  13.   s.x := 42;
  14.   Smack;
  15.   s.show; // Segfault when virtual when non virtual prints -1 not 42
  16. end;

As I suspected, it does not work. The problem is that _alloca is called from within the newInstance functions stack frame, so the memory is allocated on that stack frame. When newInstance returns the newly created instance, it's stackframe is poped and the memory is not available anymore.
In this example then the stack frame of Smack will be overlapping with the old stack frame of NewInstance, and accessing it's local variables accesses the memory where the class is located. Thats why the completely legal and in-bound FillChar on a local variable overrides the class info.

The only way to solve this is to alloca before NewInstance is used, and then be just used by NewInstance:
Code: Pascal  [Select][+][-]
  1. type
  2.   TStackObject = class
  3.   public
  4.      x: Integer;
  5.      class function newinstance : tobject;override;
  6.      procedure FreeInstance;override;
  7.      procedure show;
  8.   end;
  9.  
  10. threadvar
  11.   StackMemory: Pointer;
  12.      
  13.   class function TStackObject.NewInstance:Tobject;
  14.   begin
  15.     if StackMemory <> nil then
  16.       InitInstance(StackMemory);
  17.     NewInstance:=TObject(StackMemory);
  18.   end;
  19.  
  20.   procedure TStackObject.FreeInstance;
  21.   begin
  22.     CleanUpInstance;
  23.     // no free!
  24.   end;
  25.  
  26.   procedure TStackObject.Show;
  27.   begin
  28.     writeln(self.x);
  29.   end;
  30.  
  31. procedure Smack;
  32. var
  33.   arr: array[0..1023] of Byte;
  34. begin
  35.   FillChar(arr, SizeOf(arr), $FF);
  36. end;
  37.  
  38. procedure TestStackObject;
  39. var
  40.   s: TStackObject;
  41. begin
  42.   StackMemory := _alloca(TStackObject.InstanceSize, 4);
  43.   s := TStackObject.Create;
  44.   s.x := 42;
  45.   Smack;
  46.   s.show;
  47. end;

Another alternative would be to use ASM to move the current stack frame up within NewInstance and thereby "allocate" memory on the previous stack frame.

 

TinyPortal © 2005-2018