aboutsummaryrefslogtreecommitdiff
path: root/src/compiler.c
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2019-02-27 22:26:35 +0100
committerdec05eba <dec05eba@protonmail.com>2020-07-25 14:36:46 +0200
commit76d85a10f6cda93eba29dad5372e8160af7289c8 (patch)
treecfec3043ec45a5e83494ec109e87c239dad6cc47 /src/compiler.c
parent8201cd9f40897cf6b8e6973b28a8661108702ab1 (diff)
Use multiple threads to parse
Diffstat (limited to 'src/compiler.c')
-rw-r--r--src/compiler.c225
1 files changed, 225 insertions, 0 deletions
diff --git a/src/compiler.c b/src/compiler.c
new file mode 100644
index 0000000..fba18d3
--- /dev/null
+++ b/src/compiler.c
@@ -0,0 +1,225 @@
+#include "../include/compiler.h"
+#include "../include/parser.h"
+#include "../include/std/log.h"
+#include "../include/std/mem.h"
+#include "../include/std/alloc.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+static CHECK_RESULT int get_thread_count_env_var(int *thread_count) {
+ char *threads;
+ threads = getenv("THREADS");
+ if(!threads)
+ return -1;
+ *thread_count = atoi(threads);
+ return 0;
+}
+
+int amal_compiler_init(amal_compiler *self) {
+ int i;
+ int result;
+
+ result = get_thread_count_env_var(&self->usable_thread_count);
+ if(result != 0) {
+ self->usable_thread_count = amal_get_usable_thread_count();
+ if(self->usable_thread_count == 0) {
+ amal_log_info("Unable to get the number of threads available on the system, using 1 thread.");
+ amal_log_info("You can override the number of threads using by setting the environment variable THREADS");
+ self->usable_thread_count = 1;
+ }
+ } else if(self->usable_thread_count == 0) {
+ amal_log_info("Environment variable THREADS contains invalid number for threads. THREADS has to be at least 1.");
+ return AMAL_COMPILER_ERR;
+ }
+ /* Exclude thread count because we also use the main thread */
+ --self->usable_thread_count;
+
+ am_memset(&self->allocator, 0, sizeof(self->allocator));
+ am_memset(&self->main_thread_allocator, 0, sizeof(self->main_thread_allocator));
+ self->started = bool_false;
+ amal_mutex_init(&self->mutex);
+
+ return_if_error(scoped_allocator_init(&self->allocator));
+ cleanup_if_error(scoped_allocator_init(&self->main_thread_allocator));
+ cleanup_if_error(buffer_init(&self->parsers, &self->allocator));
+ cleanup_if_error(buffer_init(&self->queued_files, &self->allocator));
+ cleanup_if_error(scoped_allocator_alloc(&self->allocator,
+ self->usable_thread_count * sizeof(ParserThreadData),
+ (void**)&self->threads));
+ for(i = 0; i < self->usable_thread_count; ++i)
+ cleanup_if_error(parser_thread_data_init(&self->threads[i]));
+ return AMAL_COMPILER_OK;
+
+ cleanup:
+ /* Ignore result */
+ result = amal_compiler_deinit(self);
+ return AMAL_COMPILER_ERR;
+}
+
+int amal_compiler_deinit(amal_compiler *self) {
+ int i;
+ int result;
+ result = AMAL_COMPILER_OK;
+
+ for(i = 0; i < self->usable_thread_count; ++i) {
+ int r;
+ r = parser_thread_data_deinit(&self->threads[i]);
+ if(r != 0)
+ result = r;
+ }
+
+ scoped_allocator_deinit(&self->allocator);
+ scoped_allocator_deinit(&self->main_thread_allocator);
+ return result;
+}
+
+typedef struct {
+ amal_compiler *compiler;
+ ParserThreadData *parser_thread_data;
+ BufferView filepath;
+} CompilerParserThreadUserData;
+
+static CHECK_RESULT int amal_compiler_load_first_this_thread(amal_compiler *self, BufferView filepath, ScopedAllocator *allocator) {
+ Parser parser;
+ int result;
+ result = AMAL_COMPILER_ERR;
+ am_memset(&parser, 0, sizeof(parser));
+
+ return_if_error(parser_init(&parser, self, allocator));
+ cleanup_if_error(parser_parse_file(&parser, filepath));
+ amal_log_debug("Finished parsing in thread");
+ cleanup_if_error(amal_mutex_lock(&self->mutex, "amal_compiler_load_first_this_thread"));
+ cleanup_if_error(buffer_append(&self->parsers, &parser, sizeof(parser)));
+ result = AMAL_COMPILER_OK;
+
+ cleanup:
+ amal_mutex_tryunlock(&self->mutex);
+ return result;
+}
+
+/* TODO: Handle errors (stop parsing in all other threads and report errors/warnings) */
+static void* thread_callback_parse_file(void *userdata) {
+ int _;
+ BufferView next_file;
+ CompilerParserThreadUserData compiler_parser_userdata;
+ (void)_;
+ next_file = create_buffer_view_null();
+ assert(!amal_thread_is_main());
+
+ am_memcpy(&compiler_parser_userdata, userdata, sizeof(compiler_parser_userdata));
+ am_free(userdata);
+ amal_log_debug("Thread start parse file");
+ cleanup_if_error(amal_compiler_load_first_this_thread(compiler_parser_userdata.compiler,
+ compiler_parser_userdata.filepath,
+ &compiler_parser_userdata.parser_thread_data->allocator));
+ cleanup_if_error(amal_mutex_lock(&compiler_parser_userdata.compiler->mutex, "thread_callback_parse_file"));
+ if(compiler_parser_userdata.compiler->queued_files.size > 0)
+ _ = buffer_pop(&compiler_parser_userdata.compiler->queued_files, &next_file, sizeof(next_file));
+
+ cleanup:
+ amal_log_debug("Thread finished, next file: %s", next_file.data ? "yes" : "no");
+ if(!next_file.data)
+ compiler_parser_userdata.parser_thread_data->status = PARSER_THREAD_STATUS_IDLE;
+ amal_mutex_tryunlock(&compiler_parser_userdata.compiler->mutex);
+ if(next_file.data) {
+ _ = amal_compiler_load_first_this_thread(compiler_parser_userdata.compiler,
+ next_file,
+ &compiler_parser_userdata.parser_thread_data->allocator);
+ }
+ return NULL;
+}
+
+static CHECK_RESULT int amal_compiler_load_file_select_thread(amal_compiler *self, BufferView filepath, ParserThreadData **thread_selected) {
+ int i;
+ int result;
+ ParserThreadData *parser_thread_data;
+ CompilerParserThreadUserData *thread_user_data;
+ thread_user_data = NULL;
+ *thread_selected = NULL;
+ result = AMAL_COMPILER_ERR;
+ assert(amal_thread_is_main());
+
+ cleanup_if_error(amal_mutex_lock(&self->mutex, "amal_compiler_load_file_select_thread"));
+ for(i = 0; i < self->usable_thread_count; ++i) {
+ parser_thread_data = &self->threads[i];
+ if(parser_thread_data->status == PARSER_THREAD_STATUS_RUNNING)
+ continue;
+
+ cleanup_if_error(am_malloc(sizeof(CompilerParserThreadUserData), (void**)&thread_user_data));
+ thread_user_data->compiler = self;
+ thread_user_data->parser_thread_data = parser_thread_data;
+ thread_user_data->filepath = filepath;
+ result = parser_thread_data_start(parser_thread_data, thread_callback_parse_file, thread_user_data);
+ *thread_selected = parser_thread_data;
+ break;
+ }
+
+ cleanup:
+ if(result != 0)
+ am_free(thread_user_data);
+ amal_mutex_tryunlock(&self->mutex);
+ return result;
+}
+
+static CHECK_RESULT int amal_compiler_load_file_join_threads(amal_compiler *self) {
+ int i;
+ int _;
+ int result;
+ void *thread_return_data;
+ ParserThreadData *parser_thread_data;
+ result = AMAL_COMPILER_ERR;
+ (void)_;
+
+ while(self->queued_files.size > 0) {
+ BufferView filepath;
+ cleanup_if_error(amal_mutex_lock(&self->mutex, "amal_compiler_load_file_join_threads remaining files"));
+ _ = buffer_pop(&self->queued_files, &filepath, sizeof(filepath));
+ amal_mutex_tryunlock(&self->mutex);
+ if(filepath.data)
+ return_if_error(amal_compiler_load_first_this_thread(self, filepath, &self->main_thread_allocator));
+ }
+
+ for(i = 0; i < self->usable_thread_count; ++i) {
+ cleanup_if_error(amal_mutex_lock(&self->mutex, "amal_compiler_load_file_join_threads, waiting for workers"));
+ parser_thread_data = &self->threads[i];
+ amal_mutex_tryunlock(&self->mutex);
+ amal_log_debug("Joining thread %d, status %d", i, parser_thread_data->status);
+ _ = parser_thread_data_join(parser_thread_data, &thread_return_data);
+ }
+
+ result = AMAL_COMPILER_OK;
+ cleanup:
+ return result;
+}
+
+int amal_compiler_load_file(amal_compiler *self, BufferView filepath) {
+ int result;
+ ParserThreadData *parser_thread_data;
+ result = AMAL_COMPILER_ERR;
+
+ /*
+ amal_compiler_load_file is called by the user for the first file to compile
+ but also called by the parser when it sees @import
+ */
+ if(!self->started) {
+ self->started = bool_true;
+ /*amal_log_info("Parsing %.*s using %d thread(s)", (int)filepath.size, filepath.data, self->usable_thread_count);*/
+ return_if_error(amal_compiler_load_first_this_thread(self, filepath, &self->main_thread_allocator));
+ return amal_compiler_load_file_join_threads(self);
+ }
+
+ return_if_error(amal_compiler_load_file_select_thread(self, filepath, &parser_thread_data));
+ if(parser_thread_data)
+ return AMAL_COMPILER_OK;
+
+ cleanup_if_error(amal_mutex_lock(&self->mutex, "amal_compiler_load_file"));
+ cleanup_if_error(buffer_append(&self->queued_files, &filepath, sizeof(filepath)));
+ result = AMAL_COMPILER_OK;
+
+ cleanup:
+ amal_mutex_tryunlock(&self->mutex);
+ return result;
+} \ No newline at end of file