Use more platform agnostic mechanism for offset detection

The size of ByteArray is dependent on platform alignment, which is
different for example on 32 bit ARM systems (sizeof(ByteArray) is 24
bytes there, not 20 like 32 bit X86 systems).

By using the actual runtime structure to determine the byte offset, we
can make sure that we actually have a correct value on other platforms
as well.
Return NULL if object allocation fails

This can example for example when allocating a huge string and we want
to throw a memory error in that case and not segfault because we
dereference NULL.
Also use computed offset for Tuple

Same reasoning applies here as to ByteArray, alignment can result in
different layouts so using computed layouts works around this.
Switch to primitives for truncate/ftruncate

Sadly this is for now by far the easiest solution for this. The problem
is that on Linux, truncate binds to either truncate or truncate64
depending on compile time flags.

We try to make sure to use 64 bit filesystem operations so we can
support large files properly, but that means that when binding with FFI
it can bind to the wrong truncate function. This problem is especially
apparent on 32 bit ARM, where it sees a wrong offset value, creating
huge files when the specs try to truncate them.

Moving these to primitives make sure they are bound to the proper
functions at compile time. We already do this for other functions taking
an off_t parameter, such as seek to prevent this issue.
Add alignment for object allocation on non X86 platforms

X86 is very lenient with alignment, but platforms such as ARM are not.
Therefore we ensure to align on 64 bit boundaries on non X86 platforms.
This is necessary so all objects allocated are aligned to this and we
can for example use 64 bit CAS primitives for object headers.
Include time.h header for sleep fallback in atomic