Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
source_manager.c File Reference
#include "serene/rt/source_manager.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "serene/rt/context.h"
#include "serene/rt/engine.h"
#include "serene/rt/errors.h"
#include "serene/rt/fiber/io.h"
#include "serene/rt/impl/array_list.h"
#include "serene/rt/impl/hashmap.h"
#include "serene/rt/mm/interface.h"
#include "serene/utils.h"
Include dependency graph for source_manager.c:

Go to the source code of this file.

Data Structures

struct  maybe_buffer_t

Typedefs

typedef struct maybe_buffer_t maybe_buffer_t

Functions

srn_sm_t * srn_sm_init (srn_context_t *ctx)
 Initialize the source manager, as an immortal data structure.
static srn_error_t * free_each_buffer_chain (array_list_t *al, size_t n, void *data)
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.
static maybe_buffer_t read_file (srn_sm_t *sm, const char *filepath)
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.

Typedef Documentation

◆ maybe_buffer_t

typedef struct maybe_buffer_t maybe_buffer_t

Function Documentation

◆ free_each_buffer_chain()

srn_error_t * free_each_buffer_chain ( array_list_t * al,
size_t n,
void * data )
static

Definition at line 50 of file source_manager.c.

50 {
51 UNUSED(n);
52 srn_sm_buffer_t *buffer = data;
53
54 while (buffer != nullptr) {
55 srn_sm_buffer_t *next = buffer->next;
56 if (buffer->data != nullptr) {
57 FREE(al->ctx, buffer->data);
58 }
59
60 FREE(al->ctx, (void *)buffer->filename);
61 FREE(al->ctx, buffer);
62
63 buffer = next;
64 }
65 return nullptr;
66}
int n
Definition acutest.h:525
static int const char char buffer[256]
Definition acutest.h:524
#define FREE(ctx, ptr)
Definition interface.h:247
const srn_context_t * ctx
The context that owns every allocation the array list retains.
Definition array_list.h:183
#define UNUSED(x)
Definition utils.h:45
Here is the caller graph for this function:

◆ read_file()

maybe_buffer_t read_file ( srn_sm_t * sm,
const char * filepath )
inlinestatic

Definition at line 96 of file source_manager.c.

96 {
97 maybe_buffer_t result = {nullptr, nullptr, 0};
98
99 SM_TRACEPOINT(read_file, filepath);
100 int fd = open(filepath, O_RDONLY);
101
102 if (fd < 0) {
103 // Classify the failure from errno, captured before any other call can
104 // overwrite it. UNKNOWN_IO_ERROR covers errnos with no dedicated tag.
106 result.maybe_error = ERR(sm->ctx, tag, "Failed to open the file.");
107 return result;
108 }
109
110 struct stat st;
111 if (fstat(fd, &st) < 0) {
113 close(fd);
114 result.maybe_error = ERR(sm->ctx, tag, "Failed to stat the file.");
115 return result;
116 }
117
118 if (S_ISDIR(st.st_mode)) {
119 close(fd);
120 result.maybe_error =
121 ERR(sm->ctx, IS_A_DIRECTORY, "The path points to a directory, not a file.");
122 return result;
123 }
124
125 if (!S_ISREG(st.st_mode)) {
126 close(fd);
127 result.maybe_error =
128 ERR(sm->ctx, INVALID_ARGUMENT, "The path does not point to a regular file.");
129 return result;
130 }
131
132 // An empty file has no pages to map. Return success with buffer == nullptr;
133 // callers treat that as a valid zero-length source.
134 if (st.st_size == 0) {
135 close(fd);
136 return result;
137 }
138
139 unsigned char *buf =
140 srn_mm_malloc(sm->ctx->engine->mm, st.st_size + 1); // plus the null terminator
141 PANIC_IF_NULL(buf);
142
143 // A single read may return fewer bytes than the file holds, so keep reading
144 // until the size reported by `fstat` is in or the descriptor reports end of
145 // file. The terminator goes right after the bytes actually read, because
146 // that is what the reader stops on.
147 size_t total = 0;
148 while (total < (size_t)st.st_size) {
150 SRN_WITH_RETRY(r.maybe_error, {
151 r = srn_fiber_read(fd, buf + total, (size_t)st.st_size - total, -1);
152 });
153 if (HAS_ERROR(r)) {
154 close(fd);
155 srn_mm_free(sm->ctx->engine->mm, buf);
156 // The IO layer hands back one of the reactor's static errors, which
157 // must not be traced into. Own a copy and trace that.
158 result.maybe_error = ERR_FROM(sm->ctx, r.maybe_error);
159 return result;
160 }
161 if (r.count == 0) {
162 break;
163 }
164 total += r.count;
165 }
166 close(fd);
167 buf[total] = '\0';
168
169 result.buffer = buf;
170 result.len = total + 1;
171 return result;
172}
static atomic_int total
Definition 05_parallel.c:40
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 * srn_mm_malloc(srn_mm_t *mm, size_t size)
Generic allocations that do not participate in the block based pools.
Definition default.c:155
#define ERR_FROM(ctx, src)
Definition errors.h:224
#define HAS_ERROR(x)
True when x – a value carrying an ERROR_HEADER – has an error attached.
Definition errors.h:70
srn_error_tag_t
The kind of a failure.
Definition errors.h:92
@ IS_A_DIRECTORY
Definition errors.h:135
@ INVALID_ARGUMENT
The request itself is malformed (an empty POLL interest mask, for example), so retrying it unchanged ...
Definition errors.h:140
@ UNKNOWN_IO_ERROR
Definition errors.h:147
srn_error_tag_t srn_errno_to_tag(int errno_value, srn_error_tag_t default_tag)
Map a POSIX errno to error tag.
#define ERR(ctx, err, msg)
Definition errors.h:223
#define SRN_WITH_RETRY(err,...)
Run block and retry it (yielding between attempts through srn_fiber_io_retry) while err holds the tra...
Definition io.h:136
static maybe_buffer_t read_file(srn_sm_t *sm, const char *filepath)
#define SM_TRACEPOINT(...)
srn_sm_t * sm
close(fd)
auto result
The result of a byte transfer op (read/write/sendto/recvmsg/sendmsg).
Definition io.h:80
size_t count
Definition io.h:82
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
Here is the call graph for this function:
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
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.
Here is the call graph for this function:
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}
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
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 * 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
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
unsigned char * data
nullptr when the source file was empty (no mapping exists)
const char * filename
struct srn_sm_buffer_t * next
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: