diff --git a/src/check.cpp b/src/check.cpp index 4efbe2a2..5bfb3b4b 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -761,8 +761,8 @@ void NamedAttr::check(TypeChecker& checker, const ast::Node* node) { auto fn_type = fn_decl->type->isa(); if (!fn_type) checker.error(fn_decl->loc, "polymorphic functions cannot be exported"); - else if (fn_decl->type->order() > 1) - checker.error(fn_decl->loc, "higher-order functions cannot be exported"); + //else if (fn_decl->type->order() > 1) + // checker.error(fn_decl->loc, "higher-order functions cannot be exported"); else if (!fn_decl->fn->body) checker.error(fn_decl->loc, "exported functions must have a body"); else diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 57fcc34e..bf317d2d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -83,6 +83,25 @@ add_test(NAME simple_compare COMMAND artic --print-ast ${CMAKE_CURRENT_SOURC add_test(NAME simple_double_ptr COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/double_ptr.art) add_test(NAME simple_math COMMAND artic --print-ast ${CMAKE_CURRENT_SOURCE_DIR}/simple/math.art) +add_test(NAME thorin_closure_conv_no_op COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_no_op.art) +add_test(NAME thorin_closure_conv_empty_env COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_empty_env.art) +add_test(NAME thorin_closure_conv_thin_env COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_thin_env.art) +add_test(NAME thorin_closure_conv_allocated_env COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_allocated_env.art) +add_test(NAME thorin_closure_conv_capture1 COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_capture1.art) +add_test(NAME thorin_closure_conv_capture2 COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_capture2.art) +add_test(NAME thorin_closure_conv_capture_bb COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_capture_bb.art) +add_test(NAME thorin_closure_conv_capture_top_level COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_capture_top_level.art) +add_test(NAME thorin_closure_conv_rec COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_rec.art) +add_test(NAME thorin_closure_conv_rec2 COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/closure_conv_rec2.art) +add_test(NAME thorin_range_loop1 COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/range_loop1.art) +add_test(NAME thorin_range_loop2 COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/range_loop2.art) +add_test(NAME thorin_range_loop3 COMMAND artic --print-ast -g --emit-c ${CMAKE_CURRENT_SOURCE_DIR}/thorin/range_loop3.art) +add_test(NAME thorin_parallel_basic COMMAND artic --print-ast -g --emit-llvm ${CMAKE_CURRENT_SOURCE_DIR}/thorin/parallel_basic.art) +add_test(NAME thorin_parallel_functions1 COMMAND artic --print-ast -g --emit-llvm ${CMAKE_CURRENT_SOURCE_DIR}/thorin/parallel_functions1.art) +add_test(NAME thorin_parallel_functions2 COMMAND artic --print-ast -g --emit-llvm ${CMAKE_CURRENT_SOURCE_DIR}/thorin/parallel_functions2.art) +add_test(NAME thorin_parallel_functions3 COMMAND artic --print-ast -g --emit-llvm ${CMAKE_CURRENT_SOURCE_DIR}/thorin/parallel_functions3.art) +add_test(NAME thorin_busted_scheduler COMMAND artic --print-ast -g --emit-llvm ${CMAKE_CURRENT_SOURCE_DIR}/thorin/busted_scheduler.art) + add_failure_test(NAME failure_annot COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/annot.art) add_failure_test(NAME failure_comment COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/comment.art) add_failure_test(NAME failure_utf8 COMMAND artic ${CMAKE_CURRENT_SOURCE_DIR}/failure/utf8.art) diff --git a/test/codegen/helpers.c b/test/codegen/helpers.c index 4c009870..091f089b 100644 --- a/test/codegen/helpers.c +++ b/test/codegen/helpers.c @@ -75,3 +75,7 @@ void save_img(int32_t w, int32_t h, const double* img) { } free(out_row); } + +void* anydsl_alloc(int32_t dont_care, int64_t size) { + return malloc(size); +} diff --git a/test/thorin/CMakeLists.txt b/test/thorin/CMakeLists.txt index cca78ece..533e8e62 100644 --- a/test/thorin/CMakeLists.txt +++ b/test/thorin/CMakeLists.txt @@ -8,7 +8,7 @@ macro(assign_me_bool var) endif() endmacro() -function(add_thorin_test) +function(add_backend_test) cmake_parse_arguments(test "NO_C;NO_LLVM;NO_SPIRV" "NAME;SOURCE_FILE" "ARGS" ${ARGN}) assign_me_bool(TEST_USE_C NOT ${test_NO_C}) assign_me_bool(HAS_LLVM ${Thorin_HAS_LLVM_SUPPORT}) @@ -16,7 +16,7 @@ function(add_thorin_test) assign_me_bool(HAS_SPIRV ${Thorin_HAS_SPIRV_SUPPORT}) assign_me_bool(TEST_USE_SPIRV ${HAS_SPIRV} AND NOT ${test_NO_SPIRV}) - add_test(NAME thorin_${test_NAME} COMMAND ${CMAKE_COMMAND} + add_test(NAME backend_${test_NAME} COMMAND ${CMAKE_COMMAND} -DCOMPILER=$ -DC_COMPILER=${CMAKE_C_COMPILER} -DT=${test_NAME} @@ -30,8 +30,8 @@ function(add_thorin_test) -P ${PROJECT_SOURCE_DIR}/test/thorin/oracle.cmake) endfunction() -add_thorin_test(NAME hello_world SOURCE_FILE hello_world) -add_thorin_test(NAME llvm_intrinsic SOURCE_FILE llvm_intrinsic NO_C NO_SPIRV) -add_thorin_test(NAME spirv_builtin SOURCE_FILE spirv_builtin NO_C NO_LLVM) -add_thorin_test(NAME control_flow SOURCE_FILE control_flow) -add_thorin_test(NAME slot SOURCE_FILE slot) +add_backend_test(NAME hello_world SOURCE_FILE hello_world) +add_backend_test(NAME llvm_intrinsic SOURCE_FILE llvm_intrinsic NO_C NO_SPIRV) +add_backend_test(NAME spirv_builtin SOURCE_FILE spirv_builtin NO_C NO_LLVM) +add_backend_test(NAME control_flow SOURCE_FILE control_flow) +add_backend_test(NAME slot SOURCE_FILE slot) diff --git a/test/thorin/busted_scheduler.art b/test/thorin/busted_scheduler.art new file mode 100644 index 00000000..713b6b5b --- /dev/null +++ b/test/thorin/busted_scheduler.art @@ -0,0 +1,26 @@ +#[import(cc="C")] +fn bar() -> i32; + +#[import(cc="C")] +fn leak(fn(i32) -> i32) -> (); + +// this breaks the scheduler on 802e2eafbbe6cd79d4bca47c9553f315bc4568ea +// (with the inlining pass disabled) +// the old early() scheduler was not able to properly cope with recursive aggregates (the closures of f and g) +// and would end up scheduling one of them before `y` is computed. +#[export] +fn foo(z: i32) { + let y = bar(); + + fn f(x: i32) -> i32 { + g(x + y) + } + + fn g(x: i32) -> i32 { + f(x + z) + } + + leak(f); + leak(g); + 0 +} \ No newline at end of file diff --git a/test/thorin/closure_conv_allocated_env.art b/test/thorin/closure_conv_allocated_env.art new file mode 100644 index 00000000..5103ac14 --- /dev/null +++ b/test/thorin/closure_conv_allocated_env.art @@ -0,0 +1,12 @@ +#[import(cc="C")] +fn leak(fn(i32) -> (i32, i32)) -> (); + +// this time the environment is too big to fit in a pointer and must be allocated/extracted from +#[export] +fn foo(x: i32, z: i32) -> i32 { + fn f(y: i32) { (x + y, z) } + + leak(f); + + return (0) +} diff --git a/test/thorin/closure_conv_capture1.art b/test/thorin/closure_conv_capture1.art new file mode 100644 index 00000000..e85006af --- /dev/null +++ b/test/thorin/closure_conv_capture1.art @@ -0,0 +1,9 @@ +#[import(cc="C")] +fn leak2(i32, fn(i32) -> !) -> (); + +// we capture the return continuation of foo here +#[export] +fn foo() -> i32 { + leak2(5, return); + 0 +} diff --git a/test/thorin/closure_conv_capture2.art b/test/thorin/closure_conv_capture2.art new file mode 100644 index 00000000..89549bad --- /dev/null +++ b/test/thorin/closure_conv_capture2.art @@ -0,0 +1,13 @@ +#[import(cc="C")] +fn leak2(i32, fn() -> !) -> (); + +// similar to the last one but now we capture a 'break' continuation +#[export] +fn foo() -> i32 { + while true { + leak2(5, break); + } + // this basic block is unreachable from the CFG + // but should still be emitted as part of this foo's scope + 0 +} \ No newline at end of file diff --git a/test/thorin/closure_conv_capture3.art b/test/thorin/closure_conv_capture3.art new file mode 100644 index 00000000..5affbdd4 --- /dev/null +++ b/test/thorin/closure_conv_capture3.art @@ -0,0 +1,18 @@ +#[import(cc="C")] +fn out(i32) -> (); + +fn bar(a: i32, b: fn() -> ()) -> () { + out(a); + b() +} + +// we capture the return continuation of foo here +#[export] +fn foo(x: i32, y: &i32) -> i32 { + while true { + //bar(5, continue); + bar(5, break); + bar(5, break); + } + x + *y +} \ No newline at end of file diff --git a/test/thorin/closure_conv_capture_bb.art b/test/thorin/closure_conv_capture_bb.art new file mode 100644 index 00000000..95fa28b0 --- /dev/null +++ b/test/thorin/closure_conv_capture_bb.art @@ -0,0 +1,20 @@ +#[import(cc="C")] +fn leak(fn(i32) -> !) -> (); + +#[export] +fn foo(x: i32) -> i32 { + let r = return; + + // 'f' is a basic block within 'foo' + fn f(y: i32) -> ! { + if (x + y > 0) { + r(0) + } else { + r(y) + } + } + + leak(f); + + return (0) +} diff --git a/test/thorin/closure_conv_capture_top_level.art b/test/thorin/closure_conv_capture_top_level.art new file mode 100644 index 00000000..8f26709e --- /dev/null +++ b/test/thorin/closure_conv_capture_top_level.art @@ -0,0 +1,11 @@ +#[import(cc="C")] +fn leak(fn(i32) -> i32) -> (); + +#[export] +fn foo(x: i32) -> i32 { + fn f(y: i32) { 0 } + + leak(f); + + return (0) +} diff --git a/test/thorin/closure_conv_empty_env.art b/test/thorin/closure_conv_empty_env.art new file mode 100644 index 00000000..3c446b5f --- /dev/null +++ b/test/thorin/closure_conv_empty_env.art @@ -0,0 +1,11 @@ +#[import(cc="C")] +fn leak(fn(i32) -> i32) -> (); + +#[export] +fn foo(x: i32) -> i32 { + fn f(y: i32) { x } + + leak(f); + + return (0) +} diff --git a/test/thorin/closure_conv_no_op.art b/test/thorin/closure_conv_no_op.art new file mode 100644 index 00000000..5f847b3d --- /dev/null +++ b/test/thorin/closure_conv_no_op.art @@ -0,0 +1,5 @@ +// no actual closure conversion takes place, but the type of 'f' should be affected +#[export] +fn foo(f: fn(i32) -> i32) -> i32 { + f(42) +} diff --git a/test/thorin/closure_conv_rec.art b/test/thorin/closure_conv_rec.art new file mode 100644 index 00000000..68e35703 --- /dev/null +++ b/test/thorin/closure_conv_rec.art @@ -0,0 +1,21 @@ +#[import(cc="C")] +fn leak(fn(i32) -> i32) -> (); + +// we have two nested closures that can call each other +// this requires the closure for g to consume f's "self" parameter +// otherwise closure conversion just diverges +#[export] +fn foo(i: i32) -> i32 { + fn f(x: i32) -> i32 { + fn g(y: i32) -> i32 { + f(x + y + i) + } + + leak(g); + g(x) + } + + leak(f); + + 0 +} diff --git a/test/thorin/closure_conv_rec2.art b/test/thorin/closure_conv_rec2.art new file mode 100644 index 00000000..bd548f2b --- /dev/null +++ b/test/thorin/closure_conv_rec2.art @@ -0,0 +1,21 @@ +#[import(cc="C")] +fn leak(fn(i32) -> i32) -> (); + +// we have two closures who can call each other, neither is nested in the other. +// this results in two mutually recursive closures: their environments include the other. +// this is unequivocally painful to deal with. +#[export] +fn foo(i: i32) -> i32 { + fn f(x: i32) -> i32 { + g(x * i) + } + + fn g(y: i32) -> i32 { + f(y + i) + } + + leak(f); + leak(g); + + 0 +} diff --git a/test/thorin/closure_conv_thin_env.art b/test/thorin/closure_conv_thin_env.art new file mode 100644 index 00000000..979594a0 --- /dev/null +++ b/test/thorin/closure_conv_thin_env.art @@ -0,0 +1,11 @@ +#[import(cc="C")] +fn leak(fn(i32) -> i32) -> (); + +#[export] +fn foo(x: i32) -> i32 { + fn f(y: i32) { x + y } + + leak(f); + + return (0) +} diff --git a/test/thorin/parallel_basic.art b/test/thorin/parallel_basic.art new file mode 100644 index 00000000..9a340d21 --- /dev/null +++ b/test/thorin/parallel_basic.art @@ -0,0 +1,10 @@ +#[import(cc = "thorin", name = "parallel")] fn thorin_parallel(_num_threads: i32, _lower: i32, _upper: i32, _body: fn(i32) -> ()) -> (); +fn @parallel(body: fn(i32) -> ()) = @|num_threads: i32, lower: i32, upper: i32| thorin_parallel(num_threads, lower, upper, body); + +#[export] +fn parallel_test(n: i32, a: &[i32], b: &[i32], c: &mut[i32]) -> i32 { + for i in parallel(4, 0, n) { + c(i) = a(i) + b(i); + } + 0 +} \ No newline at end of file diff --git a/test/thorin/parallel_functions1.art b/test/thorin/parallel_functions1.art new file mode 100644 index 00000000..8c573728 --- /dev/null +++ b/test/thorin/parallel_functions1.art @@ -0,0 +1,12 @@ +#[import(cc = "thorin", name = "parallel")] fn thorin_parallel(_num_threads: i32, _lower: i32, _upper: i32, _body: fn(i32) -> ()) -> (); +fn @parallel(body: fn(i32) -> ()) = @|num_threads: i32, lower: i32, upper: i32| thorin_parallel(num_threads, lower, upper, body); + +fn mul(x: i32, y: i32) = x * y; + +#[export] +fn parallel_test(n: i32, a: &[i32], b: &[i32], c: &mut[i32]) -> i32 { + for i in parallel(4, 0, n) { + c(i) = mul(a(i), a(i)) + mul(b(i), b(i)); + } + 0 +} \ No newline at end of file diff --git a/test/thorin/parallel_functions2.art b/test/thorin/parallel_functions2.art new file mode 100644 index 00000000..bd72cc1c --- /dev/null +++ b/test/thorin/parallel_functions2.art @@ -0,0 +1,17 @@ +#[import(cc = "thorin", name = "parallel")] fn thorin_parallel(_num_threads: i32, _lower: i32, _upper: i32, _body: fn(i32) -> ()) -> (); +fn @parallel(body: fn(i32) -> ()) = @|num_threads: i32, lower: i32, upper: i32| thorin_parallel(num_threads, lower, upper, body); + +#[import(cc="C")] +fn leak(fn(i32) -> i32) -> (); + +#[export] +fn parallel_test(n: i32, a: &[i32], b: &[i32], c: &mut[i32]) -> i32 { + fn f(x: i32) = x * n; + + for i in parallel(4, 0, n) { + // this requires lifting the local continuation 'f' + leak(f); + c(i) = f(a(i)) + f(b(i)); + } + 0 +} \ No newline at end of file diff --git a/test/thorin/parallel_functions3.art b/test/thorin/parallel_functions3.art new file mode 100644 index 00000000..56dbc522 --- /dev/null +++ b/test/thorin/parallel_functions3.art @@ -0,0 +1,16 @@ +#[import(cc = "thorin", name = "parallel")] fn thorin_parallel(_num_threads: i32, _lower: i32, _upper: i32, _body: fn(i32) -> ()) -> (); +fn @parallel(body: fn(i32) -> ()) = @|num_threads: i32, lower: i32, upper: i32| thorin_parallel(num_threads, lower, upper, body); + +#[import(cc="C")] +fn magic() -> fn(i32) -> i32; + +#[export] +fn parallel_test(n: i32, a: &[i32], b: &[i32], c: &mut[i32]) -> i32 { + // this produces a function pointer, it should be spillable without CC + let f = magic(); + + for i in parallel(4, 0, n) { + c(i) = f(a(i)) + f(b(i)); + } + 0 +} \ No newline at end of file diff --git a/test/thorin/parallel_test.c b/test/thorin/parallel_test.c new file mode 100644 index 00000000..64eee651 --- /dev/null +++ b/test/thorin/parallel_test.c @@ -0,0 +1,21 @@ +#include +#include +#include + +void parallel_test(int, int*, int*, int*); + +int main(int argc, char** argv) { + int n = 4096 * 1024; // 4M elements + int* a = malloc(sizeof(int) * n); + int* b = malloc(sizeof(int) * n); + int* c = malloc(sizeof(int) * n); + for (int i = 0; i < n; i++) { + a[i] = i % 100; + b[i] = 100 - i % 100; + } + parallel_test(n, a, b, c); + for (int i = 0; i < n; i++) { + assert(c[i] == 100); + } + printf("It werks.\n"); +} \ No newline at end of file diff --git a/test/thorin/range_loop1.art b/test/thorin/range_loop1.art new file mode 100644 index 00000000..dc1608c7 --- /dev/null +++ b/test/thorin/range_loop1.art @@ -0,0 +1,20 @@ +fn @range(body: fn (i32) -> ()) { + fn loop(a: i32, b: i32) -> () { + if a < b { + body(a); + loop(a + 1, b) + } + } + loop +} + +#[export] +fn foo(count: i32, buf: &mut[i32]) -> i32 { + let mut acc = 0; + fn inner(i: i32) { + acc += buf(i); + } + range(inner)(0, count); + acc +} + diff --git a/test/thorin/range_loop2.art b/test/thorin/range_loop2.art new file mode 100644 index 00000000..abd0d277 --- /dev/null +++ b/test/thorin/range_loop2.art @@ -0,0 +1,21 @@ +fn @range(body: fn (i32) -> ()) { + fn loop(a: i32, b: i32) -> () { + if a < b { + body(a); + loop(a + 1, b) + } + } + loop +} + +#[export] +fn foo(count: i32, buf: &mut[i32]) -> i32 { + let mut acc = 0; + fn inner(i: i32) { + acc += buf(i); + } + range(inner)(0, count); + range(inner)(0, count); // we now use 'inner' in two places so it cannot be inlined! + acc +} + diff --git a/test/thorin/range_loop3.art b/test/thorin/range_loop3.art new file mode 100644 index 00000000..f14299cf --- /dev/null +++ b/test/thorin/range_loop3.art @@ -0,0 +1,21 @@ +fn @range(body: fn (i32) -> ()) { + fn loop(a: i32, b: i32) -> () { + if a < b { + body(a); + loop(a + 1, b) + } + } + loop +} + +#[export] +fn foo(count: i32, buf: &mut[i32]) -> i32 { + let mut acc = 0; + fn @inner(i: i32) { // we have marked 'inner' for PE, and after inlining 'range', we are left with first-order calls to inner, which get PE'd + acc += buf(i); + } + range(inner)(0, count); + range(inner)(0, count); + acc +} + diff --git a/test/thorin/tail_rec_elimination.art b/test/thorin/tail_rec_elimination.art new file mode 100644 index 00000000..a27f14cf --- /dev/null +++ b/test/thorin/tail_rec_elimination.art @@ -0,0 +1,13 @@ +#[export] +fn pow(x: i32, y: i32) -> i32 { + return (pow_tailrec(x, y, 1)) +} + +#[export] +fn pow_tailrec(x: i32, n: i32, acc: i32) -> i32 { + if n > 1 { + pow_tailrec(x, n - 1, acc * x) + } else { + acc + } +} \ No newline at end of file diff --git a/test/thorin/test.art b/test/thorin/test.art new file mode 100644 index 00000000..8dd77dac --- /dev/null +++ b/test/thorin/test.art @@ -0,0 +1,18 @@ +#[import(cc="C")] +fn puts(&[u8]) -> i32; + +#[import(cc="C", name="printf")] +fn printfd(&[u8], i32) -> (); + +#[export] +fn leak(f: fn(i32) -> i32) -> () { + printfd("fn(7) = %d\n", f(7)); +} + +#[import(cc="C")] +fn foo(x: i32) -> i32; + +#[export] +fn main(f: fn(i32) -> i32) -> () { + foo(4); +}