aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/gc-alloc.hh
blob: afdd7eeb0cb2179c52fe36a2e30eb765c3f16190 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#pragma once
/// @file Aliases and wrapper functions that are transparently GC-enabled
/// if Lix is compiled with BoehmGC enabled.

#include <cstddef>
#include <cstring>
#include <list>
#include <map>
#include <new>
#include <string_view>
#include <vector>

#include "checked-arithmetic.hh"

#if HAVE_BOEHMGC
#include <functional> // std::less
#include <utility> // std::pair
#define GC_INCLUDE_NEW
#include <gc/gc.h>
#include <gc/gc_allocator.h>
#include <gc/gc_cpp.h>

/// calloc, transparently GC-enabled.
#define LIX_GC_CALLOC(size) GC_MALLOC(size)

/// strdup, transaprently GC-enabled.
#define LIX_GC_STRDUP(str) GC_STRDUP(str)

/// Atomic GC malloc() with GC enabled, or regular malloc() otherwise.
#define LIX_GC_MALLOC_ATOMIC(size) GC_MALLOC_ATOMIC(size)

namespace nix
{

/// Alias for std::map which uses BoehmGC's allocator conditional on this Lix
/// build having GC enabled.
template<typename KeyT, typename ValueT>
using GcMap = std::map<
    KeyT,
    ValueT,
    std::less<KeyT>,
    traceable_allocator<std::pair<KeyT const, ValueT>>
>;

/// Alias for std::vector which uses BoehmGC's allocator conditional on this Lix
/// build having GC enabled.
template<typename ItemT>
using GcVector = std::vector<ItemT, traceable_allocator<ItemT>>;

/// Alias for std::list which uses BoehmGC's allocator conditional on this Lix
/// build having GC enabled.
template<typename ItemT>
using GcList = std::list<ItemT, traceable_allocator<ItemT>>;

}

#else

#include <cstdlib>

/// calloc, transparently GC-enabled.
#define LIX_GC_CALLOC(size) calloc(size, 1)

/// strdup, transparently GC-enabled.
#define LIX_GC_STRDUP(str) strdup(str)

/// Atomic GC malloc() with GC enabled, or regular malloc() otherwise.
/// The returned memory must never contain pointers.
#define LIX_GC_MALLOC_ATOMIC(size) malloc(size)

namespace nix
{

/// Alias for std::map which uses BoehmGC's allocator conditional on this Lix
/// build having GC enabled.
template<typename KeyT, typename ValueT>
using GcMap = std::map<KeyT, ValueT>;

/// Alias for std::vector which uses BoehmGC's allocator conditional on this Lix
/// build having GC enabled.
template<typename ItemT>
using GcVector = std::vector<ItemT>;

/// Alias for std::list which uses BoehmGC's allocator conditional on this Lix
/// build having GC enabled.
template<typename ItemT>
using GcList = std::list<ItemT>;

}

#endif

namespace nix
{

[[gnu::always_inline]]
inline void * gcAllocBytes(size_t n)
{
    // Note: various places expect the allocated memory to be zero.
    // Hence: calloc().
    void * ptr = LIX_GC_CALLOC(n);
    if (ptr == nullptr) {
        throw std::bad_alloc();
    }

    return ptr;
}

/// Typed, safe wrapper around calloc() (transparently GC-enabled). Allocates
/// enough for the requested count of the specified type. Also checks for
/// nullptr (and throws @ref std::bad_alloc), and casts the void pointer to
/// a pointer of the specified type, for type-convenient goodness.
template<typename T>
[[gnu::always_inline]]
inline T * gcAllocType(size_t howMany = 1)
{
    // NOTE: size_t * size_t, which can definitely overflow.
    // Unsigned integer overflow is definitely a bug, but isn't undefined
    // behavior, so we can just check if we overflowed after the fact.
    // However, people can and do request zero sized allocations, so we need
    // to check that neither of our multiplicands were zero before complaining
    // about it.
    // NOLINTNEXTLINE(bugprone-sizeof-expression): yeah we only seem to alloc pointers with this. the calculation *is* correct though!
    auto checkedSz = checked::Checked<size_t>(howMany) * sizeof(T);
    size_t sz = checkedSz.valueWrapping();
    if (checkedSz.overflowed()) {
        // Congrats, you done did an overflow.
        throw std::bad_alloc();
    }

    return static_cast<T *>(gcAllocBytes(sz));
}

/// GC-transparently allocates a buffer for a C-string of @ref size *bytes*,
/// meaning you should include the size needed by the NUL terminator in the
/// passed size. Memory allocated with this function must never contain other
/// pointers.
inline char * gcAllocString(size_t size)
{
    char * cstr = static_cast<char *>(LIX_GC_MALLOC_ATOMIC(size));
    if (cstr == nullptr) {
        throw std::bad_alloc();
    }
    return cstr;
}

/// Returns a C-string copied from @ref toCopyFrom, or a single, static empty
/// string if @ref toCopyFrom is also empty.
char const * gcCopyStringIfNeeded(std::string_view toCopyFrom);

}