diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index c53a5ba..a5141e3 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -2,9 +2,9 @@ name: build-and-test on: push: - branches: [ master ] + branches: [ master, development ] pull_request: - branches: [ master ] + branches: [ master, development ] jobs: build-and-test: diff --git a/include/artic/ast.h b/include/artic/ast.h index 584d740..401450d 100644 --- a/include/artic/ast.h +++ b/include/artic/ast.h @@ -419,6 +419,17 @@ struct TypeApp : public Type { void print(Printer&) const override; }; +/// The codomain of functions that don't return anything. +struct NoCodomType : public Type { + NoCodomType(const Loc& loc) + : Type(loc) + {} + + const artic::Type* infer(TypeChecker&) override; + void bind(NameBinder&) override; + void print(Printer&) const override; +}; + /// Type resulting from a parsing error. struct ErrorType : public Type { ErrorType(const Loc& loc) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7948c1d..9500adb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,3 +49,6 @@ if (Thorin_HAS_LLVM_SUPPORT) target_compile_definitions(artic PUBLIC -DENABLE_LLVM) llvm_config(artic ${AnyDSL_LLVM_LINK_SHARED} core support) endif () +if (Thorin_HAS_SPIRV_SUPPORT) + target_compile_definitions(artic PUBLIC -DENABLE_SPIRV) +endif () diff --git a/src/bind.cpp b/src/bind.cpp index a09d9be..37c4a3f 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -132,6 +132,8 @@ void TypeApp::bind(NameBinder& binder) { binder.bind(path); } +void NoCodomType::bind(NameBinder&) {} + void ErrorType::bind(NameBinder&) {} // Statements ---------------------------------------------------------------------- diff --git a/src/check.cpp b/src/check.cpp index 606d860..fa6f01f 100644 --- a/src/check.cpp +++ b/src/check.cpp @@ -422,8 +422,13 @@ bool TypeChecker::check_filter(const ast::Expr& expr) { is_mutable = true; else return true; - } else if (expr.isa()) + } else if (expr.isa()) { return true; + } else if (auto proj = expr.isa()) { + //This needs to be supported to inspect struct and tuple members. + //TODO: Not sure if this check coveres all possible problematic cases. + return check_filter(*proj->expr); + } error(expr.loc, "unsupported expression in filter"); if (is_logic_or) @@ -885,6 +890,8 @@ const artic::Type* UnsizedArrayType::infer(TypeChecker& checker) { } const artic::Type* FnType::infer(TypeChecker& checker) { + if (to->isa()) + return checker.type_table.cn_type(checker.infer(*from)); return checker.type_table.fn_type(checker.infer(*from), checker.infer(*to)); } @@ -901,6 +908,10 @@ const artic::Type* TypeApp::infer(TypeChecker& checker) { return path.type = path.infer(checker, false); } +const artic::Type* NoCodomType::infer(TypeChecker& checker) { + return checker.type_table.no_ret_type(); +} + // Statements ---------------------------------------------------------------------- const artic::Type* DeclStmt::infer(TypeChecker& checker) { diff --git a/src/emit.cpp b/src/emit.cpp index 331a8fa..fb6660a 100644 --- a/src/emit.cpp +++ b/src/emit.cpp @@ -1658,7 +1658,7 @@ const thorin::Def* StaticDecl::emit(Emitter& emitter) const { const thorin::Def* FnDecl::emit(Emitter& emitter) const { auto _ = emitter.save_state(); - const thorin::FnType* cont_type = nullptr; + const artic::FnType* fn_type = nullptr; Emitter::MonoFn mono_fn { this, {} }; if (type_params) { for (auto& param : type_params->params) @@ -1667,12 +1667,12 @@ const thorin::Def* FnDecl::emit(Emitter& emitter) const { if (auto it = emitter.mono_fns.find(mono_fn); it != emitter.mono_fns.end()) return it->second; emitter.poly_defs.emplace_back(); - cont_type = type->as()->body->convert(emitter)->as(); + fn_type = type->as()->body->as(); } else { - cont_type = type->convert(emitter)->as(); + fn_type = type->as(); } - auto cont = emitter.world.continuation(cont_type, emitter.debug_info(*this)); + auto cont = emitter.world.continuation(fn_type->convert(emitter)->as(), emitter.debug_info(*this)); if (type_params) emitter.mono_fns.emplace(std::move(mono_fn), cont); @@ -1715,7 +1715,7 @@ const thorin::Def* FnDecl::emit(Emitter& emitter) const { fn->def = def = cont; emitter.enter(cont); - emitter.emit(*fn->param, emitter.tuple_from_params(cont, true)); + emitter.emit(*fn->param, emitter.tuple_from_params(cont, !fn_type->codom->isa())); if (fn->filter) cont->set_filter(emitter.world.filter(thorin::Array(cont->num_params(), emitter.emit(*fn->filter)))); auto value = emitter.emit(*fn->body); @@ -1893,7 +1893,7 @@ std::string PtrType::stringify(Emitter& emitter) const { } const thorin::Type* PtrType::convert(Emitter& emitter) const { - return emitter.world.ptr_type(pointee->convert(emitter), 1, -1, thorin::AddrSpace(addr_space)); + return emitter.world.ptr_type(pointee->convert(emitter), 1, thorin::AddrSpace(addr_space)); } std::string ImplicitParamType::stringify(Emitter& emitter) const { diff --git a/src/main.cpp b/src/main.cpp index 9ce2464..5cc035e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,9 @@ #ifdef ENABLE_LLVM #include #endif +#ifdef ENABLE_SPIRV +#include +#endif using namespace artic; @@ -94,9 +97,11 @@ struct ProgramOptions { bool print_ast = false; bool emit_thorin = false; bool emit_c_int = false; + bool emit_host_code = false; bool emit_c = false; bool emit_json = false; bool emit_llvm = false; + bool emit_spirv = false; std::string host_triple; std::string host_cpu; std::string host_attr; @@ -195,12 +200,22 @@ struct ProgramOptions { tab_width = std::strtoull(argv[++i], NULL, 10); } else if (matches(argv[i], "--emit-llvm")) { #ifdef ENABLE_LLVM + emit_host_code = true; emit_llvm = true; #else log::error("Thorin is built without LLVM support, use '--emit-c' instead"); return false; +#endif + } else if (matches(argv[i], "--emit-spirv")) { +#ifdef ENABLE_SPIRV + emit_host_code = true; + emit_spirv = true; +#else + log::error("Thorin is built without SPIR-V support"); + return false; #endif } else if (matches(argv[i], "--emit-c")) { + emit_host_code = true; emit_c = true; } else if (matches(argv[i], "--host-triple")) { if (!check_arg(argc, argv, i)) @@ -341,41 +356,48 @@ int main(int argc, char** argv) { thorin::c::emit_c_int(thorin, stream); } } - if (opts.opt_level > 1 || opts.emit_c || opts.emit_llvm) + if (opts.opt_level > 1 || opts.emit_host_code) thorin.opt(); if (opts.emit_thorin) thorin.world().dump_scoped(!opts.no_color); - if (opts.emit_json || opts.emit_c || opts.emit_llvm) { - auto emit_to_file = [&] (thorin::CodeGen& cg) { - auto name = opts.module_name + cg.file_ext(); - std::ofstream file(name); - if (!file) - log::error("cannot open '{}' for writing", name); - else - cg.emit_stream(file); - }; + + auto emit_to_file = [&] (thorin::CodeGen& cg) { + auto name = opts.module_name + cg.file_ext(); + std::ofstream file(name); + if (!file) + log::error("cannot open '{}' for writing", name); + else + cg.emit_stream(file); + }; #ifdef ENABLE_JSON - if (opts.emit_json) { - thorin::json::CodeGen cg(thorin, opts.debug, opts.host_triple, opts.host_cpu, opts.host_attr); + if (opts.emit_json) { + thorin::json::CodeGen cg(thorin, opts.debug, opts.host_triple, opts.host_cpu, opts.host_attr); + emit_to_file(cg); + } +#endif + if (opts.emit_host_code) { + thorin::DeviceBackends backends(thorin.world(), opts.opt_level, opts.debug, opts.hls_flags); + if (opts.emit_c) { + thorin::Cont2Config kernel_configs; + thorin::c::CodeGen cg(thorin, kernel_configs, thorin::c::Lang::C99, opts.debug, opts.hls_flags); emit_to_file(cg); } -#endif - if (opts.emit_c || opts.emit_llvm) { - thorin::DeviceBackends backends(thorin.world(), opts.opt_level, opts.debug, opts.hls_flags); - if (opts.emit_c) { - thorin::Cont2Config kernel_configs; - thorin::c::CodeGen cg(thorin, kernel_configs, thorin::c::Lang::C99, opts.debug, opts.hls_flags); - emit_to_file(cg); - } #ifdef ENABLE_LLVM - if (opts.emit_llvm) { - thorin::llvm::CPUCodeGen cg(thorin, opts.opt_level, opts.debug, opts.host_triple, opts.host_cpu, opts.host_attr); - emit_to_file(cg); - } + if (opts.emit_llvm) { + thorin::llvm::CPUCodeGen cg(thorin, opts.opt_level, opts.debug, opts.host_triple, opts.host_cpu, opts.host_attr); + emit_to_file(cg); + } +#endif +#ifdef ENABLE_SPIRV + if (opts.emit_spirv) { + thorin::spirv::Target target; + + thorin::spirv::CodeGen cg(thorin, target, opts.debug); + emit_to_file(cg); + } #endif - for (auto& cg : backends.cgs) { - if (cg) emit_to_file(*cg); - } + for (auto& cg : backends.cgs) { + if (cg) emit_to_file(*cg); } } return EXIT_SUCCESS; diff --git a/src/parser.cpp b/src/parser.cpp index 589d2df..7ea2510 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -85,8 +85,12 @@ Ptr Parser::parse_fn_decl() { error(ahead().loc(), "parameter list expected in function definition"); Ptr ret_type; - if (accept(Token::Arrow)) - ret_type = parse_type(); + if (accept(Token::Arrow)) { + if (accept(Token::Not)) + ret_type = make_ptr(prev_); + else + ret_type = parse_type(); + } Ptr body; if (ahead().tag() == Token::LBrace) @@ -1087,7 +1091,11 @@ Ptr Parser::parse_fn_type() { else from = parse_error_type(); expect(Token::Arrow); - auto to = parse_type(); + Ptr to; + if (accept(Token::Not)) + to = make_ptr(prev_); + else + to = parse_type(); return make_ptr(tracker(), std::move(from), std::move(to)); } diff --git a/src/print.cpp b/src/print.cpp index 15f3377..d28cc94 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -675,10 +675,8 @@ void UnsizedArrayType::print(Printer& p) const { void FnType::print(Printer& p) const { p << log::keyword_style("fn") << ' '; print_parens(p, from); - if (to) { - p << " -> "; - to->print(p); - } + p << " -> "; + to->print(p); } void PtrType::print(Printer& p) const { @@ -698,6 +696,10 @@ void TypeApp::print(Printer& p) const { path.print(p); } +void NoCodomType::print(artic::Printer& p) const { + p << "!"; +} + void ErrorType::print(Printer& p) const { p << log::error_style(""); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a1e3f70..6aa0316 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -158,7 +158,7 @@ if (Thorin_HAS_LLVM_SUPPORT) cmake_minimum_required(VERSION 3.14 FATAL_ERROR) if (NOT TARGET clang) - find_package(Clang REQUIRED CONFIG PATHS ${LLVM_DIR}/../clang NO_DEFAULT_PATH) + find_package(Clang REQUIRED CONFIG PATHS ${LLVM_DIR}/../clang) endif() # Compile the helper functions into an object file @@ -231,6 +231,8 @@ if (Thorin_HAS_LLVM_SUPPORT) SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/codegen/compare.art) endif () +add_subdirectory(thorin) + if (CODE_COVERAGE AND CMAKE_BUILD_TYPE STREQUAL "Debug") set(COVERAGE_EXCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/codegen/*") setup_target_for_coverage_gcovr_html( diff --git a/test/thorin/CMakeLists.txt b/test/thorin/CMakeLists.txt new file mode 100644 index 0000000..cca78ec --- /dev/null +++ b/test/thorin/CMakeLists.txt @@ -0,0 +1,37 @@ +# silly hack because CMake has no plain expressions in its grammar :( +# https://stackoverflow.com/questions/62487808/can-i-set-a-cmake-variable-to-the-result-of-a-boolean-expression +macro(assign_me_bool var) + if(${ARGN}) + set(${var} ON) + else() + set(${var} OFF) + endif() +endmacro() + +function(add_thorin_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}) + assign_me_bool(TEST_USE_LLVM ${HAS_LLVM} AND NOT ${test_NO_LLVM}) + 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} + -DCOMPILER=$ + -DC_COMPILER=${CMAKE_C_COMPILER} + -DT=${test_NAME} + "-DTARGS=${test_ARGS}" + -DSRC=${CMAKE_CURRENT_SOURCE_DIR}/${test_SOURCE_FILE}.art + -DDST=${CMAKE_CURRENT_BINARY_DIR} + -DC=${TEST_USE_C} + -DLLVM=${TEST_USE_LLVM} + -DSPIRV=${TEST_USE_SPIRV} + -DMSVC=${MSVC} + -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) diff --git a/test/thorin/control_flow.art b/test/thorin/control_flow.art new file mode 100644 index 0000000..493435a --- /dev/null +++ b/test/thorin/control_flow.art @@ -0,0 +1,17 @@ +#[export] +fn test_branch(x: i32) -> i32 { + if (x > 0) { + 1 / x + } else { + 0 + } +} + +#[export] +fn test_switch(x: i32) -> i32 { + match (x) { + 0 => x, + 1 => 1, + _ => 1 / x + } +} \ No newline at end of file diff --git a/test/thorin/hello_world.art b/test/thorin/hello_world.art new file mode 100644 index 0000000..af4f1cf --- /dev/null +++ b/test/thorin/hello_world.art @@ -0,0 +1,4 @@ +#[export] +fn hello() -> i32 { + 42 +} diff --git a/test/thorin/llvm_intrinsic.art b/test/thorin/llvm_intrinsic.art new file mode 100644 index 0000000..dedd062 --- /dev/null +++ b/test/thorin/llvm_intrinsic.art @@ -0,0 +1,6 @@ +#[import(cc = "device", name = "llvm.exp.f32")] fn cpu_expf(_: f32) -> f32; + +#[export] +fn foo(f: f32) -> f32 { + cpu_expf(f - 0.5) + 0.5 +} diff --git a/test/thorin/oracle.cmake b/test/thorin/oracle.cmake new file mode 100644 index 0000000..e70ed41 --- /dev/null +++ b/test/thorin/oracle.cmake @@ -0,0 +1,41 @@ +if (C) + set(OPTIONAL_EMIT_C "--emit-c") +else () + set(OPTIONAL_EMIT_C "") +endif() + +if (LLVM) + set(OPTIONAL_EMIT_LLVM "--emit-llvm") +else () + set(OPTIONAL_EMIT_LLVM "") +endif() + +if (SPIRV) + set(OPTIONAL_EMIT_SPIRV "--emit-spirv") +else () + set(OPTIONAL_EMIT_SPIRV "") +endif() + +execute_process(COMMAND ${COMPILER} ${SRC} -O1 ${OPTIONAL_EMIT_C} ${OPTIONAL_EMIT_LLVM} ${OPTIONAL_EMIT_SPIRV} ${TARGS} COMMAND_ERROR_IS_FATAL ANY COMMAND_ECHO STDOUT) +message("Ran thorin on :${SRC}") + +if (C AND NOT MSVC) + execute_process(COMMAND ${C_COMPILER} ${DST}/${T}.c -c COMMAND_ERROR_IS_FATAL ANY COMMAND_ECHO STDOUT) + message("Validated C output for ${T}") +endif() + +if (LLVM) + find_program(LLC "llc") + if (LLC) + execute_process(COMMAND llc ${DST}/${T}.ll COMMAND_ERROR_IS_FATAL ANY COMMAND_ECHO STDOUT) + message("Validated LLVM output for ${T}") + endif() +endif() + +if (SPIRV) + find_program(SPIRV_VALIDATOR "spirv-val") + if (SPIRV_VALIDATOR) + execute_process(COMMAND ${SPIRV_VALIDATOR} ${DST}/${T}.spv COMMAND_ERROR_IS_FATAL ANY) + message("Validated SPIR-V output for ${T}") + endif () +endif() \ No newline at end of file diff --git a/test/thorin/slot.art b/test/thorin/slot.art new file mode 100644 index 0000000..a820928 --- /dev/null +++ b/test/thorin/slot.art @@ -0,0 +1,8 @@ +#[export] +fn main(i: i32) -> i32 { + let mut x = 1; + while (x < x) { + x *= 2; + } + x +} diff --git a/test/thorin/spirv_builtin.art b/test/thorin/spirv_builtin.art new file mode 100644 index 0000000..4d3ce03 --- /dev/null +++ b/test/thorin/spirv_builtin.art @@ -0,0 +1,11 @@ +#[import(cc = "device", name = "spirv.builtin")] fn spirv_get_builtin[T](i32) -> T; + +fn get_global_invocation_id() -> simd[u32 * 3] { + *spirv_get_builtin[&mut addrspace(8) simd[u32 * 3]](28 /* BuiltInGlobalInvocationId */) +} + +#[export] +fn main() -> u32 { + let id = get_global_invocation_id(); + id(0) * id(1) * id(2) +} \ No newline at end of file