Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
source_manager.c
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <lxsameer@lxsameer.com>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
20
21#include <errno.h>
22#include <fcntl.h>
23#include <limits.h>
24#include <string.h>
25#include <sys/stat.h>
26#include <unistd.h>
27
28#include "serene/rt/context.h"
29#include "serene/rt/engine.h"
30#include "serene/rt/errors.h"
31#include "serene/rt/fiber/io.h"
35#include "serene/utils.h"
36
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}
49
50static srn_error_t *free_each_buffer_chain(array_list_t *al, size_t n, void *data) {
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}
67
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}
76
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}
89
90typedef struct maybe_buffer_t {
92 unsigned char *buffer;
93 size_t len;
95
96static inline maybe_buffer_t read_file(srn_sm_t *sm, const char *filepath) {
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}
173
174srn_sm_maybe_id_t srn_sm_read_buffer_file(srn_sm_t *sm, const char *filepath, bool reread) {
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}
281
282srn_sm_maybe_buffer_t srn_sm_buffer_file(srn_sm_t *sm, const char *filepath, bool reread) {
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}
static atomic_int total
Definition 05_parallel.c:40
int n
Definition acutest.h:525
static int const char char buffer[256]
Definition acutest.h:524
if(!acutest_colorize_)
Definition acutest.h:532
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
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
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
A bit-partitioned trie with a tail buffer, used as a growable indexed sequence in the runtime.
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
Error handling for the runtime.
#define PUSH_AND_MOVE(ctx, from, to)
Definition errors.h:73
#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
hmap_t hmap_empty(const srn_context_t *ctx)
Create, initialize and return a new hashmap pinned to ctx.
Definition hashmap.c:655
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
This is an implementation of Compressed Hash-Array Mapped Prefix-tree, which is a bit-partitioned,...
#define srn_mm_immortal_allocate(mm, T)
Definition interface.h:188
#define MALLOC(ctx, T)
Definition interface.h:235
#define FREE(ctx, ptr)
Definition interface.h:247
The fiber facing IO API: blocking looking calls a fiber uses to do IO.
#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
void srn_fiber_mutex_init(srn_fiber_mutex_t *m)
Initialise m to the unlocked state.
Definition mutex.c:76
#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)
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 e...
void srn_sm_shutdown(srn_sm_t *sm)
Shutdown the source manager freeing all the buffers.
srn_sm_t * srn_sm_init(srn_context_t *ctx)
Initialize the source manager, as an immortal data structure.
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 srn_error_t * free_each_buffer_chain(array_list_t *al, size_t n, void *data)
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.
Source manager is what that read the source files and own the buffer that contains the data.
#define SM_TRACEPOINT(...)
size_t srn_sm_buffer_id_t
#define SRN_SM_NO_ID
srn_sm_t * sm
sm buffer_counts
close(fd)
auto result
const srn_context_t * ctx
The context that owns every allocation the array list retains.
Definition array_list.h:183
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 * buffer
srn_engine_t * engine
Long term state of the compiler.
Definition context.h:49
srn_mm_t * mm
Memory manager.
Definition engine.h:68
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:173
The result of a byte transfer op (read/write/sendto/recvmsg/sendmsg).
Definition io.h:80
size_t count
Definition io.h:82
unsigned char * data
nullptr when the source file was empty (no mapping exists)
const char * filename
struct srn_sm_buffer_t * next
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
#define PANIC_IF(cond, msg)
Definition utils.h:59
#define UNUSED(x)
Definition utils.h:45
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
#define PANIC(msg)
Definition utils.h:53