From: Brendan Hansen Date: Mon, 3 Jan 2022 19:28:08 +0000 (-0600) Subject: added experimental iter.parallel_for X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=7aff4c3b6e1c3eaff1efc079ddaf39646a033acd;p=onyx.git added experimental iter.parallel_for --- diff --git a/core/container/iter.onyx b/core/container/iter.onyx index a6bc7ee3..f75ce56f 100644 --- a/core/container/iter.onyx +++ b/core/container/iter.onyx @@ -506,6 +506,7 @@ count :: #match {} some :: #match {} #match some macro (it: $T, cond: $F) -> #auto where Iterable(T) { some :: some + as_iterator :: as_iterator return some(as_iterator(it), cond); } @@ -517,6 +518,7 @@ some :: #match {} every :: #match {} #match every macro (it: $T, cond: $F) -> #auto where Iterable(T) { every :: every + as_iterator :: as_iterator return every(as_iterator(it), cond); } @@ -533,3 +535,61 @@ to_array :: (it: Iterator($T), allocator := context.allocator) -> [..] T { return arr; } + + + +distributor :: #match {} +#match distributor macro (it: $T) -> #auto where Iterable(T) { + distributor :: distributor; + as_iterator :: as_iterator; + return distributor(as_iterator(it)); +} + +#match distributor (it: Iterator($T)) -> Iterator(T) { + Context :: struct (T: type_expr) { + mutex: sync.Mutex; + iterator: Iterator(T); + } + + next :: (use c: ^Context($T)) -> (T, bool) { + sync.scoped_mutex(^mutex); + return take_one(iterator); + } + + close :: (use c: ^Context($T)) { + sync.mutex_destroy(^c.mutex); + cfree(c); + } + + c := new(Context(T)); + sync.mutex_init(^c.mutex); + c.iterator = it; + + return .{c, #solidify next {T=T}, #solidify close {T=T}}; +} + +parallel_for :: macro (iterable: $I, thread_count: u32, body: Code) where Iterable(I) { + thread :: package core.thread; + alloc :: package core.alloc; + + if thread_count != 0 { + dist := distributor(iterable); + hacky_crap_to_get_the_type_of_T(dist); + } + + hacky_crap_to_get_the_type_of_T :: macro (dist: Iterator($T)) { + threads := (cast(^thread.Thread) alloc.from_stack(thread_count * sizeof thread.Thread))[0 .. (thread_count - 1)]; + for^ threads do thread.spawn(it, ^dist, #solidify thread_function {body=body, T=T}); + + thread_function(body, ^dist); + + for^ threads do thread.join(it); + dist.close(dist.data); + } + + thread_function :: ($body: Code, iter: ^Iterator($T)) { + for #no_close *iter { + #insert body; + } + } +} \ No newline at end of file diff --git a/examples/18_macros.onyx b/examples/18_macros.onyx index 1461f160..35cf47f8 100644 --- a/examples/18_macros.onyx +++ b/examples/18_macros.onyx @@ -110,6 +110,28 @@ main :: (args: [] cstr) { // As another small shorthand, if a code block only contains one expression (like the code block above), // then you can write by wrapping the expression in #(...). triple_macro(#(println("Short code block!"))); + + // As yet another small piece of syntactic sugar, you can pass a code block to a macro or procedure in + // the following way: + + cool_block :: macro (param: i32, body: Code) { + local_variable := param * 2; + #insert body; + } + + cool_block(10) { + println(local_variable); + } + + // Whenever there is a call at the statement level, the compiler will look one token ahead and see if there + // is a '{'. If there is, then the compiler will parse the '{' as the start of a code block, and will pass that + // code block as the last non-named argument to the function call. This allows you to write blocks of code that are + // prefaced by a controlling macro, which enables you do whatever crazy things you can imagine. For example, you + // could do something like `#pragma omp parallel for` in Onyx without relying on compiler magic. + // + // parallel_for(iterable, thread_count=4) { + // println(it); // Will be automatically assigned from the called macro. + // } } #load "core/std" diff --git a/include/astnodes.h b/include/astnodes.h index 3b352d6d..1269ad62 100644 --- a/include/astnodes.h +++ b/include/astnodes.h @@ -1652,6 +1652,8 @@ Type* polymorphic_struct_lookup(AstPolyStructType* ps_type, bh_arr(AstPolySoluti // NOTE: Useful inlined functions static inline b32 is_lval(AstNode* node) { + node = strip_aliases(node); + if ((node->kind == Ast_Kind_Local) || (node->kind == Ast_Kind_Param) || (node->kind == Ast_Kind_Global) diff --git a/scripts/run_tests.onyx b/scripts/run_tests.onyx index b36bf39d..87b2da06 100644 --- a/scripts/run_tests.onyx +++ b/scripts/run_tests.onyx @@ -117,42 +117,9 @@ find_onyx_files :: (root: str, cases: ^[..] Test_Case) { return; } -at_least_one_test_failed := false; - -test_cases :: (cases) => { - for #no_close *cases { - printf("[{}] Running test {}...\n", context.thread_id, it.source_file); - - proc := os.process_spawn(onyx_cmd, .["run", it.source_file]); - defer os.process_destroy(^proc); - - proc_reader := io.reader_make(^proc); - output := io.read_all(^proc_reader); - defer memory.free_slice(^output); - - if exit := os.process_wait(^proc); exit != .Success { - // Error running the test case - print_color(.Red, "[{}] Error '{}' in test case {}.\n{}", context.thread_id, exit, it.source_file, output); - at_least_one_test_failed = true; - continue; - } - - for expected_file: os.with_file(it.expected_file) { - expected_reader := io.reader_make(expected_file); - expected_output := io.read_all(^expected_reader); - - if output != expected_output { - print_color(.Red, "[{}] Output did not match for {}.\n", context.thread_id, it.source_file); - printf("Expected:\n{}\n", expected_output); - printf("Got:\n{}\n", output); - at_least_one_test_failed = true; - } - } - } -} - // The executable to use when compiling onyx_cmd: str; +at_least_one_test_failed := false; settings := Settings.{}; Settings :: struct { @@ -160,7 +127,7 @@ Settings :: struct { debug := false; ["--threads"] - threads := 3; + threads := 4; ["--no-color"] no_color := false; @@ -238,29 +205,46 @@ main :: (args) => { find_onyx_files(settings.test_folder, ^cases); thread_count := settings.threads; - case_iterator := distributor(cases); - if thread_count > 0 { - // Stack allocate this in the future? - threads := memory.make_slice(thread.Thread, thread_count); + iter.parallel_for(cases, settings.threads) { + // Weird macros mean I have to forward external names + use package core + onyx_cmd :: onyx_cmd + print_color :: print_color + at_least_one_test_failed :: at_least_one_test_failed - for thread_count { - thread.spawn(^threads[it], ^case_iterator, test_cases); - } + printf("[{}] Running test {}...\n", context.thread_id, it.source_file); - test_cases(^case_iterator); + proc := os.process_spawn(onyx_cmd, .["run", it.source_file]); + defer os.process_destroy(^proc); - for ^ threads do thread.join(it); + proc_reader := io.reader_make(^proc); + output := io.read_all(^proc_reader); + defer memory.free_slice(^output); - } else { - test_cases(^case_iterator); - } + if exit := os.process_wait(^proc); exit != .Success { + // Error running the test case + print_color(.Red, "[{}] Error '{}' in test case {}.\n{}", context.thread_id, exit, it.source_file, output); + at_least_one_test_failed = true; + continue; + } + + for expected_file: os.with_file(it.expected_file) { + expected_reader := io.reader_make(expected_file); + expected_output := io.read_all(^expected_reader); - case_iterator->close(); + if output != expected_output { + print_color(.Red, "[{}] Output did not match for {}.\n", context.thread_id, it.source_file); + printf("Expected:\n{}\n", expected_output); + printf("Got:\n{}\n", output); + at_least_one_test_failed = true; + } + } + } if at_least_one_test_failed { print_color(.Red, "FAILED\n"); - // (package wasi).proc_exit(1); + os.exit(-1); } else { print_color(.Green, "SUCCESS\n");