Blame docs/dev-ast-memory.md

Packit Service 7770af
# LibSass smart pointer implementation
Packit Service 7770af
Packit Service 7770af
LibSass uses smart pointers very similar to `shared_ptr` known
Packit Service 7770af
by Boost or C++11. Implementation is a bit less modular since
Packit Service 7770af
it was not needed. Various compile time debug options are
Packit Service 7770af
available if you need to debug memory life-cycles.
Packit Service 7770af
Packit Service 7770af
Packit Service 7770af
## Memory Classes
Packit Service 7770af
Packit Service 7770af
### SharedObj
Packit Service 7770af
Packit Service 7770af
Base class for the actual node implementations. This ensures
Packit Service 7770af
that every object has a reference counter and other values.
Packit Service 7770af
Packit Service 7770af
```c++
Packit Service 7770af
class AST_Node : public SharedObj { ... };
Packit Service 7770af
```
Packit Service 7770af
Packit Service 7770af
### SharedPtr (base class for SharedImpl)
Packit Service 7770af
Packit Service 7770af
Base class that holds on to the pointer. The reference counter
Packit Service 7770af
is stored inside the pointer object directly (`SharedObj`).
Packit Service 7770af
Packit Service 7770af
### SharedImpl (inherits from SharedPtr)
Packit Service 7770af
Packit Service 7770af
This is the main base class for objects you use in your code. It
Packit Service 7770af
will make sure that the memory it points at will be deleted once
Packit Service 7770af
all copies to the same object/memory go out of scope.
Packit Service 7770af
Packit Service 7770af
```c++
Packit Service 7770af
Class* pointer = new Class(...);
Packit Service 7770af
SharedImpl<Class> obj(pointer);
Packit Service 7770af
```
Packit Service 7770af
Packit Service 7770af
To spare the developer of typing the templated class every time,
Packit Service 7770af
we created typedefs for each available AST Node specialization.
Packit Service 7770af
Packit Service 7770af
```c++
Packit Service 7770af
typedef SharedImpl<Number> Number_Obj;
Packit Service 7770af
Number_Obj number = SASS_MEMORY_NEW(...);
Packit Service 7770af
```
Packit Service 7770af
Packit Service 7770af
Packit Service 7770af
## Memory life-cycles
Packit Service 7770af
Packit Service 7770af
### Pointer pickups
Packit Service 7770af
Packit Service 7770af
I often use the terminology of "pickup". This means the moment when
Packit Service 7770af
a raw pointer not under any control is assigned to a reference counted
Packit Service 7770af
object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be
Packit Service 7770af
automatically released once the object goes out of scope (but only
Packit Service 7770af
if the reference counter reaches zero). Main point beeing, you don't
Packit Service 7770af
have to worry about memory management yourself.
Packit Service 7770af
Packit Service 7770af
### Object detach
Packit Service 7770af
Packit Service 7770af
Sometimes we can't return reference counted objects directly (see
Packit Service 7770af
invalid covariant return types problems below). But we often still
Packit Service 7770af
need to use reference objects inside a function to avoid leaks when
Packit Service 7770af
something throws. For this you can use `detach`, which basically
Packit Service 7770af
detaches the pointer memory from the reference counted object. So
Packit Service 7770af
when the reference counted object goes out of scope, it will not
Packit Service 7770af
free the attached memory. You are now again in charge of freeing
Packit Service 7770af
the memory (just assign it to a reference counted object again).
Packit Service 7770af
Packit Service 7770af
Packit Service 7770af
## Circular references
Packit Service 7770af
Packit Service 7770af
Reference counted memory implementations are prone to circular references.
Packit Service 7770af
This can be addressed by using a multi generation garbage collector. But
Packit Service 7770af
for our use-case that seems overkill. There is no way so far for users
Packit Service 7770af
(sass code) to create circular references. Therefore we can code around
Packit Service 7770af
this possible issue. But developers should be aware of this limitation.
Packit Service 7770af
Packit Service 7770af
There are AFAIR two places where circular references could happen. One is
Packit Service 7770af
the `sources` member on every `Selector`. The other one can happen in the
Packit Service 7770af
extend code (Node handling). The easy way to avoid this is to only assign
Packit Service 7770af
complete object clones to these members. If you know the objects lifetime
Packit Service 7770af
is longer than the reference you create, you can also just store the raw
Packit Service 7770af
pointer. Once needed this could be solved with weak pointers.
Packit Service 7770af
Packit Service 7770af
Packit Service 7770af
## Addressing the invalid covariant return types problems
Packit Service 7770af
Packit Service 7770af
If you are not familiar with the mentioned problem, you may want
Packit Service 7770af
to read up on covariant return types and virtual functions, i.e.
Packit Service 7770af
Packit Service 7770af
- http://stackoverflow.com/questions/6924754/return-type-covariance-with-smart-pointers
Packit Service 7770af
- http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers
Packit Service 7770af
- http://stackoverflow.com/questions/2687790/how-to-accomplish-covariant-return-types-when-returning-a-shared-ptr
Packit Service 7770af
Packit Service 7770af
We hit this issue at least with the CRTP visitor pattern (eval, expand,
Packit Service 7770af
listize and so forth). This means we cannot return reference counted
Packit Service 7770af
objects directly. We are forced to return raw pointers or we would need
Packit Service 7770af
to have a lot of explicit and expensive upcasts by callers/consumers.
Packit Service 7770af
Packit Service 7770af
### Simple functions that allocate new AST Nodes
Packit Service 7770af
Packit Service 7770af
In the parser step we often create new objects and can just return a
Packit Service 7770af
unique pointer (meaning ownership clearly shifts back to the caller).
Packit Service 7770af
The caller/consumer is responsible that the memory is freed.
Packit Service 7770af
Packit Service 7770af
```c++
Packit Service 7770af
typedef Number* Number_Ptr;
Packit Service 7770af
int parse_integer() {
Packit Service 7770af
  ... // do the parsing
Packit Service 7770af
  return 42;
Packit Service 7770af
}
Packit Service 7770af
Number_Ptr parse_number() {
Packit Service 7770af
  Number_Ptr p_nr = SASS_MEMORY_NEW(...);
Packit Service 7770af
  p_nr->value(parse_integer());
Packit Service 7770af
  return p_nr;
Packit Service 7770af
}
Packit Service 7770af
Number_Obj nr = parse_number();
Packit Service 7770af
```
Packit Service 7770af
Packit Service 7770af
The above would be the encouraged pattern for such simple cases.
Packit Service 7770af
Packit Service 7770af
### Allocate new AST Nodes in functions that can throw
Packit Service 7770af
Packit Service 7770af
There is a major caveat with the previous example, considering this
Packit Service 7770af
more real-life implementation that throws an error. The throw may
Packit Service 7770af
happen deep down in another function. Holding raw pointers that
Packit Service 7770af
we need to free would leak in this case.
Packit Service 7770af
Packit Service 7770af
```c++
Packit Service 7770af
int parse_integer() {
Packit Service 7770af
  ... // do the parsing
Packit Service 7770af
  if (error) throw(error);
Packit Service 7770af
  return 42;
Packit Service 7770af
}
Packit Service 7770af
```
Packit Service 7770af
Packit Service 7770af
With this `parse_integer` function the previous example would leak memory.
Packit Service 7770af
I guess it is pretty obvious, as the allocated memory will not be freed,
Packit Service 7770af
as it was never assigned to a SharedObj value. Therefore the above code
Packit Service 7770af
would better be written as:
Packit Service 7770af
Packit Service 7770af
```c++
Packit Service 7770af
typedef Number* Number_Ptr;
Packit Service 7770af
int parse_integer() {
Packit Service 7770af
  ... // do the parsing
Packit Service 7770af
  if (error) throw(error);
Packit Service 7770af
  return 42;
Packit Service 7770af
}
Packit Service 7770af
// this leaks due to pointer return
Packit Service 7770af
// should return Number_Obj instead
Packit Service 7770af
// though not possible for virtuals!
Packit Service 7770af
Number_Ptr parse_number() {
Packit Service 7770af
  Number_Obj nr = SASS_MEMORY_NEW(...);
Packit Service 7770af
  nr->value(parse_integer()); // throws
Packit Service 7770af
  return &nr; // Ptr from Obj
Packit Service 7770af
}
Packit Service 7770af
Number_Obj nr = parse_number();
Packit Service 7770af
// will now be freed automatically
Packit Service 7770af
```
Packit Service 7770af
Packit Service 7770af
The example above unfortunately will not work as is, since we return a
Packit Service 7770af
`Number_Ptr` from that function. Therefore the object allocated inside
Packit Service 7770af
the function is already gone when it is picked up again by the caller.
Packit Service 7770af
The easy fix for the given simplified use case would be to change the
Packit Service 7770af
return type of `parse_number` to `Number_Obj`. Indeed we do it exactly
Packit Service 7770af
this way in the parser. But as stated above, this will not work for
Packit Service 7770af
virtual functions due to invalid covariant return types!
Packit Service 7770af
Packit Service 7770af
### Return managed objects from virtual functions
Packit Service 7770af
Packit Service 7770af
The easy fix would be to just create a new copy on the heap and return
Packit Service 7770af
that. But this seems like a very inelegant solution to this problem. I
Packit Service 7770af
mean why can't we just tell the object to treat it like a newly allocated
Packit Service 7770af
object? And indeed we can. I've added a `detach` method that will tell
Packit Service 7770af
the object to survive deallocation until the next pickup. This means
Packit Service 7770af
that it will leak if it is not picked up by consumer.
Packit Service 7770af
Packit Service 7770af
```c++
Packit Service 7770af
typedef Number* Number_Ptr;
Packit Service 7770af
int parse_integer() {
Packit Service 7770af
  ... // do the parsing
Packit Service 7770af
  if (error) throw(error);
Packit Service 7770af
  return 42;
Packit Service 7770af
}
Packit Service 7770af
Number_Ptr parse_number() {
Packit Service 7770af
  Number_Obj nr = SASS_MEMORY_NEW(...);
Packit Service 7770af
  nr->value(parse_integer()); // throws
Packit Service 7770af
  return nr.detach();
Packit Service 7770af
}
Packit Service 7770af
Number_Obj nr = parse_number();
Packit Service 7770af
// will now be freed automatically
Packit Service 7770af
```
Packit Service 7770af
Packit Service 7770af
Packit Service 7770af
## Compile time debug options
Packit Service 7770af
Packit Service 7770af
To enable memory debugging you need to define `DEBUG_SHARED_PTR`.
Packit Service 7770af
This can i.e. be done in `include/sass/base.h`
Packit Service 7770af
Packit Service 7770af
```c++
Packit Service 7770af
define DEBUG_SHARED_PTR
Packit Service 7770af
```
Packit Service 7770af
Packit Service 7770af
This will print lost memory on exit to stderr. You can also use
Packit Service 7770af
`setDbg(true)` on sepecific variables to emit reference counter
Packit Service 7770af
increase, decrease and other events.
Packit Service 7770af
Packit Service 7770af
Packit Service 7770af
## Why reinvent the wheel when there is `shared_ptr` from C++11
Packit Service 7770af
Packit Service 7770af
First, implementing a smart pointer class is not really that hard. It
Packit Service 7770af
was indeed also a learning experience for myself. But there are more
Packit Service 7770af
profound advantages:
Packit Service 7770af
Packit Service 7770af
- Better GCC 4.4 compatibility (which most code still has OOTB)
Packit Service 7770af
- Not thread safe (give us some free performance on some compiler)
Packit Service 7770af
- Beeing able to track memory allocations for debugging purposes
Packit Service 7770af
- Adding additional features if needed (as seen in `detach`)
Packit Service 7770af
- Optional: optimized weak pointer implementation possible
Packit Service 7770af
Packit Service 7770af
### Thread Safety
Packit Service 7770af
Packit Service 7770af
As said above, this is not thread safe currently. But we don't need
Packit Service 7770af
this ATM anyway. And I guess we probably never will share AST Nodes
Packit Service 7770af
across different threads.