Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
source_manager.h File Reference

Source manager is what that read the source files and own the buffer that contains the data. More...

#include <stddef.h>
#include "serene/rt/fiber/mutex.h"
#include "serene/rt/fiber/thread.h"
#include "serene/rt/impl/array_list.h"
#include "serene/rt/impl/hashmap.h"
#include "serene/utils.h"
Include dependency graph for source_manager.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  srn_sm_maybe_id_t
struct  srn_sm_buffer_t
struct  srn_sm_maybe_buffer_t
struct  srn_sm_t

Macros

#define SRN_SM_NO_ID   UINTPTR_MAX
#define SM_TRACEPOINT(...)

Typedefs

typedef size_t srn_sm_buffer_id_t
typedef struct srn_sm_maybe_id_t srn_sm_maybe_id_t
typedef struct srn_sm_buffer_t srn_sm_buffer_t
typedef struct srn_sm_maybe_buffer_t srn_sm_maybe_buffer_t
typedef struct srn_sm_t srn_sm_t

Functions

srn_sm_t * srn_sm_init (srn_context_t *ctx)
 Initialize the source manager, as an immortal data structure.
void srn_sm_shutdown (srn_sm_t *sm)
 Shutdown the source manager freeing all the buffers.
srn_sm_buffer_t * srn_sm_get_buffer (srn_sm_t *sm, srn_sm_buffer_id_t id)
 Return the buffer pointer of the given id, nullptr if the id does not exists.
srn_sm_maybe_id_t srn_sm_read_buffer_file (srn_sm_t *sm, const char *filepath, bool reread)
 Read and buffer the given filepath into the source manager and return the buffer id.
srn_sm_maybe_buffer_t srn_sm_buffer_file (srn_sm_t *sm, const char *filepath, bool reread)
 Read and buffer the given filepath into the source manager and return a srn_sm_maybe_buffer_t which either fails or has a pointer to the buffer containing the content of the file.
static const unsigned char * srn_sm_buffer_data (srn_sm_buffer_t *buf)
static size_t srn_sm_buffer_len (srn_sm_buffer_t *buf)

Detailed Description

Source manager is what that read the source files and own the buffer that contains the data.

Notes:

  • We use manual memory management for buffers since the size of buffers are not known in advance and they might be bigger than block sizes, also the lifecycle of buffers are known and we can avoidarena allocation completely.
  • Each buffer is assigned a stable integer ID on first load. That ID is the canonical handle for all later references and never changes for a given file path.
  • A hashmap from file path to ID prevents duplicate reads. Passing an already loaded path to srn_sm_read_buffer_file returns the existing ID without reopening the file.
  • When reread is true, the freshly read buffer is prepended to the existing buffer's chain rather than replacing it. The slot ID stays the same. The previous buffer remains reachable via buffer->next this way expressions that are previously loaded will continue to have valid locations.
  • An empty file produces a valid buffer slot with data == nullptr and len == 0. Callers must treat a null data field as a valid zero length source rather than an error.
  • File content is read through srn_fiber_read, so the call suspends the current fiber instead of blocking the OS thread.
  • A single write lock serialises all insertions and updates. A new file is read outside the lock and the map is checked again under it before the slot is inserted, so two fibers loading the same path end up sharing one slot. The path lookup snapshots the map under the lock; srn_sm_get_buffer is unsynchronised, callers must not call it concurrently with a write.
  • The source manager itself is allocated in the immortal pool and lives for the entire lifetime of the context.

Definition in file source_manager.h.

Macro Definition Documentation

◆ SM_TRACEPOINT

#define SM_TRACEPOINT ( ...)
Value:
SRN_TRACEPOINT_WITH_GROUP(source_manager __VA_OPT__(, ) __VA_ARGS__)
#define SRN_TRACEPOINT_WITH_GROUP(group, name,...)
Definition trace.h:80

Definition at line 63 of file source_manager.h.

◆ SRN_SM_NO_ID

#define SRN_SM_NO_ID   UINTPTR_MAX

Definition at line 61 of file source_manager.h.

Typedef Documentation

◆ srn_sm_buffer_id_t

typedef size_t srn_sm_buffer_id_t

Definition at line 66 of file source_manager.h.

◆ srn_sm_buffer_t

typedef struct srn_sm_buffer_t srn_sm_buffer_t

◆ srn_sm_maybe_buffer_t

typedef struct srn_sm_maybe_buffer_t srn_sm_maybe_buffer_t

◆ srn_sm_maybe_id_t

typedef struct srn_sm_maybe_id_t srn_sm_maybe_id_t

◆ srn_sm_t

typedef struct srn_sm_t srn_sm_t

Function Documentation

◆ srn_sm_buffer_data()

const unsigned char * srn_sm_buffer_data ( srn_sm_buffer_t * buf)
inlinestatic

Definition at line 131 of file source_manager.h.

131{ return buf->data; }
unsigned char * data
nullptr when the source file was empty (no mapping exists)
Here is the caller graph for this function:

◆ srn_sm_buffer_file()

srn_sm_maybe_buffer_t srn_sm_buffer_file ( srn_sm_t * sm,
const char * filepath,
bool reread )

Read and buffer the given filepath into the source manager and return a srn_sm_maybe_buffer_t which either fails or has a pointer to the buffer containing the content of the file.

A nullptr for the buffer means an empty file.

Definition at line 282 of file source_manager.c.

282 {
283 srn_sm_maybe_buffer_t result = {nullptr, nullptr};
284 auto maybe_id = srn_sm_read_buffer_file(sm, filepath, reread);
285
286 if (HAS_ERROR(maybe_id)) {
287 PUSH_AND_MOVE(sm->ctx, maybe_id, result);
288 return result;
289 }
290
291 // NOTE: buffer can be nullptr, it means an empty file
292 result.buffer = srn_sm_get_buffer(sm, maybe_id.id);
293
294 return result;
295}
#define PUSH_AND_MOVE(ctx, from, to)
Definition errors.h:73
#define HAS_ERROR(x)
True when x – a value carrying an ERROR_HEADER – has an error attached.
Definition errors.h:70
srn_sm_buffer_t * srn_sm_get_buffer(srn_sm_t *sm, srn_sm_buffer_id_t id)
Return the buffer pointer of the given id, nullptr if the id does not exists.
srn_sm_maybe_id_t srn_sm_read_buffer_file(srn_sm_t *sm, const char *filepath, bool reread)
Read and buffer the given filepath into the source manager and return the buffer id.
srn_sm_t * sm
auto result
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_sm_buffer_len()

size_t srn_sm_buffer_len ( srn_sm_buffer_t * buf)
inlinestatic

Definition at line 132 of file source_manager.h.

132{ return buf->len; }
Here is the caller graph for this function:

◆ srn_sm_get_buffer()

srn_sm_buffer_t * srn_sm_get_buffer ( srn_sm_t * sm,
srn_sm_buffer_id_t id )

Return the buffer pointer of the given id, nullptr if the id does not exists.

Definition at line 77 of file source_manager.c.

77 {
78 if ((id >= 0) && (id < sm->buffer_counts)) {
79 SM_TRACEPOINT(get_buffer, id);
80 auto maybe_buffer = array_list_get(&sm->buffers, id);
81 PANIC_IF(HAS_ERROR(maybe_buffer), "Can't look up the source buffer id");
82
83 srn_sm_buffer_t *buffer = maybe_buffer.data;
84 return buffer;
85 }
86
87 PANIC("Source buffer id is not in the correct range");
88}
static int const char char buffer[256]
Definition acutest.h:524
array_list_maybe_element_t array_list_get(const array_list_t *al, array_list_index_t n)
Negative index is not supported.
Definition array_list.c:235
#define SM_TRACEPOINT(...)
sm buffer_counts
#define PANIC_IF(cond, msg)
Definition utils.h:59
#define PANIC(msg)
Definition utils.h:53
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_sm_init()

srn_sm_t * srn_sm_init ( srn_context_t * ctx)

Initialize the source manager, as an immortal data structure.

The buffers themselves will NOT be managed by the the memory manager.

Definition at line 37 of file source_manager.c.

37 {
38
40 sm->ctx = ctx;
41 sm->buffers = array_list_empty(ctx);
42 sm->buffer_counts = 0;
43 sm->file_to_id_map = hmap_empty(ctx);
44
45 srn_fiber_mutex_init(&sm->write_lock);
46
47 return sm;
48}
array_list_t array_list_empty(const srn_context_t *ctx)
Create an empty array list in the given context ctx.
Definition array_list.c:101
hmap_t hmap_empty(const srn_context_t *ctx)
Create, initialize and return a new hashmap pinned to ctx.
Definition hashmap.c:655
#define srn_mm_immortal_allocate(mm, T)
Definition interface.h:188
void srn_fiber_mutex_init(srn_fiber_mutex_t *m)
Initialise m to the unlocked state.
Definition mutex.c:76
srn_engine_t * engine
Long term state of the compiler.
Definition context.h:49
srn_mm_t * mm
Memory manager.
Definition engine.h:68
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_sm_read_buffer_file()

srn_sm_maybe_id_t srn_sm_read_buffer_file ( srn_sm_t * sm,
const char * filepath,
bool reread )

Read and buffer the given filepath into the source manager and return the buffer id.

If the file already exists, it will just return the buffer id, unless the reread is true, which will reread the file and populate the buffer. It will NOT overwrite the old buffer.

Definition at line 174 of file source_manager.c.

174 {
176 result.maybe_error = nullptr;
177
178 SM_TRACEPOINT(read_buffer_file, filepath, reread);
179
180 hmap_key_t k;
181 k.data = (void *)filepath;
182 k.len = strnlen(filepath, PATH_MAX);
183
184 // Snapshot the map struct under the lock, an insert replaces the two word
185 // struct in place and an unsynchronised reader could see a torn root and
186 // length pair. CHAMT nodes are immutable, so the walk runs outside it.
187 hmap_t snapshot;
188 WITH_LOCK(&sm->write_lock, { snapshot = sm->file_to_id_map; });
189 auto maybe_buf_id = hmap_lookup(&snapshot, &k, (void *)SRN_SM_NO_ID);
190
191 if ((uintptr_t)maybe_buf_id == SRN_SM_NO_ID) {
192 // It's a new file
193 SM_TRACEPOINT(read_buffer_file_new);
194 auto maybe_buffer = read_file(sm, filepath);
195
196 if (HAS_ERROR(maybe_buffer)) {
197 PUSH_AND_MOVE(sm->ctx, maybe_buffer, result);
198 return result;
199 }
200
202 PANIC_IF_NULL(buf);
203
204 buf->data = maybe_buffer.buffer;
205 buf->len = maybe_buffer.len;
206 buf->next = nullptr;
207 // Buffers live until shutdown, the caller's path string does not.
208 buf->filename = srn_dup_str(sm->ctx->engine->mm, filepath);
210
211 bool lost_race = false;
212 WITH_LOCK(&sm->write_lock, {
213 // The file was read outside the lock, so another fiber may have loaded
214 // the same path in the meantime. Its slot wins and ours is dropped
215 // below, which keeps one slot per path.
216 void *again = hmap_lookup(&sm->file_to_id_map, &k, (void *)SRN_SM_NO_ID);
217 if ((uintptr_t)again != SRN_SM_NO_ID) {
218 result.id = (srn_sm_buffer_id_t)(uintptr_t)again;
219 lost_race = true;
220 } else if (sm->buffer_counts >= UINT16_MAX) {
221 // We need to limit the buffer ids to UINT16_MAX since the location stores the id as
222 // a 16bit number. If we ever need more than that, we should adjust the limit both here
223 // and in the location struct.
224 result.maybe_error =
225 ERR(sm->ctx, SM_BUFFER_ID_EXHAUSTED, "source manager: buffer ID space exhausted");
226 } else {
227 // The slot goes in before the map entry, so a failed push leaves no
228 // id in the map that points at nothing. No `return` inside this
229 // block, the lock is only released after it.
230 result.maybe_error = array_list_push(&sm->buffers, buf);
231 if (result.maybe_error == nullptr) {
232 sm->file_to_id_map = hmap_insert(&sm->file_to_id_map, &k, (void *)sm->buffer_counts);
233 result.id = sm->buffer_counts;
234 sm->buffer_counts++;
235 }
236 }
237 });
238
239 if (HAS_ERROR(result) || lost_race) {
240 srn_mm_free(sm->ctx->engine->mm, buf->data);
241 srn_mm_free(sm->ctx->engine->mm, (void *)buf->filename);
242 srn_mm_free(sm->ctx->engine->mm, buf);
243 }
244 return result;
245 }
246
247 auto buffer_id = (srn_sm_buffer_id_t)maybe_buf_id;
248 // We have already read this file once.
249 auto buffer = srn_sm_get_buffer(sm, buffer_id);
250
251 // At this point buffer being null is a bug
253
254 if (reread) {
255 auto maybe_new_buffer = read_file(sm, filepath);
256 if (HAS_ERROR(maybe_new_buffer)) {
257 PUSH_AND_MOVE(sm->ctx, maybe_new_buffer, result);
258 return result;
259 }
260
262 PANIC_IF_NULL(buf);
263
264 buf->data = maybe_new_buffer.buffer;
265 buf->len = maybe_new_buffer.len;
266 buf->next = buffer;
267 buf->filename = srn_dup_str(sm->ctx->engine->mm, filepath);
269
270 WITH_LOCK(&sm->write_lock, {
271 /* sm->file_to_id_map = hmap_insert(&sm->file_to_id_map, &k, (void *)buffer_id); */
272
273 auto update_err = array_list_update(&sm->buffers, buffer_id, buf);
274 PANIC_IF(update_err != nullptr, "failed to update source buffer slot");
275 });
276 }
277
278 result.id = buffer_id;
279 return result;
280}
if(!acutest_colorize_)
Definition acutest.h:532
void srn_mm_free(srn_mm_t *mm, void *ptr)
Release a pointer previously returned by srn_mm_malloc or srn_mm_reallocate.
Definition default.c:168
void * hmap_lookup(const hmap_t *hmap, const hmap_key_t *k, void *default_value)
Lookup the given k in the given hmap and return the value if it's been found.
Definition hashmap.c:665
#define MALLOC(ctx, T)
Definition interface.h:235
#define WITH_LOCK(lock, body)
Run body with lock held.
Definition mutex.h:84
static maybe_buffer_t read_file(srn_sm_t *sm, const char *filepath)
size_t srn_sm_buffer_id_t
#define SRN_SM_NO_ID
Note: For key equality we use the memcpy function.
Definition hashmap.h:66
void * data
len 0 -> data == nullptr
Definition hashmap.h:68
size_t len
Definition hashmap.h:69
const char * filename
struct srn_sm_buffer_t * next
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
char * srn_dup_str(srn_mm_t *mm, const char *s)
Copy a null terminated C string into a fresh allocation made through mm.
Definition utils.c:69
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_sm_shutdown()

void srn_sm_shutdown ( srn_sm_t * sm)

Shutdown the source manager freeing all the buffers.

After this call all the pointers into the buffers will be invalid.

Definition at line 68 of file source_manager.c.

68 {
69 auto err = array_list_for_each(&sm->buffers, free_each_buffer_chain);
70 if (err != nullptr) {
71 PANIC(err->msg);
72 }
73 // buffers and file_to_id_map are arena-allocated and freed with the context
74 sm->buffer_counts = 0;
75}
srn_error_t * array_list_for_each(array_list_t *al, array_list_for_each_f_t f)
Run the give function f for every element in the given array list al.
Definition array_list.c:327
static srn_error_t * free_each_buffer_chain(array_list_t *al, size_t n, void *data)
Here is the call graph for this function:
Here is the caller graph for this function: