added experimental iter.parallel_for
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 3 Jan 2022 19:28:08 +0000 (13:28 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 3 Jan 2022 19:28:08 +0000 (13:28 -0600)
core/container/iter.onyx
examples/18_macros.onyx
include/astnodes.h
scripts/run_tests.onyx

index a6bc7ee30337bc62263a0963dcfb2cc91941ccd6..f75ce56f71b28d9707b540fe67a7a96d2db35f29 100644 (file)
@@ -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
index 1461f16047c71bccf12cce033650e46becc7d647..35cf47f8ee5d68f39401e54f7a593ad95ef35752 100644 (file)
@@ -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"
index 3b352d6d880eb87cf9b8efeee2d552295f7bc21d..1269ad62fb8027acecc51a43d935104da87f31c9 100644 (file)
@@ -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)
index b36bf39d9da052700cc533b1412e9ca5c681f1a4..87b2da06b9f809b8345e1944357ee4eaba9d3a40 100644 (file)
@@ -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");