diff --git a/include/pareas/compiler/ast.hpp b/include/pareas/compiler/ast.hpp new file mode 100644 index 0000000..bfd9cbb --- /dev/null +++ b/include/pareas/compiler/ast.hpp @@ -0,0 +1,68 @@ +#ifndef _PAREAS_COMPILER_AST_HPP +#define _PAREAS_COMPILER_AST_HPP + +#include "futhark_generated.h" +#include "pareas_grammar.hpp" + +#include +#include + +#include + +// Keep in sync with src/compiler/datanode_types.fut +enum class DataType : uint8_t { + INVALID = 0, + VOID = 1, + INT = 2, + FLOAT = 3, + INT_REF = 4, + FLOAT_REF = 5, +}; + +const char* data_type_name(DataType dt); + +struct HostAst { + size_t num_nodes; + size_t num_functions; + + std::unique_ptr node_types; + std::unique_ptr parents; + std::unique_ptr node_data; + std::unique_ptr data_types; + std::unique_ptr node_depths; + std::unique_ptr child_indexes; + + std::unique_ptr fn_tab; + + void dump_dot(std::ostream& os) const; +}; + +struct DeviceAst { + futhark_context* ctx; + + futhark_u8_1d* node_types; + futhark_i32_1d* parents; + futhark_u32_1d* node_data; + futhark_u8_1d* data_types; + futhark_i32_1d* node_depths; + futhark_i32_1d* child_indexes; + + futhark_i32_1d* fn_tab; + + explicit DeviceAst(futhark_context* ctx); + + DeviceAst(const DeviceAst&) = delete; + DeviceAst& operator=(const DeviceAst&) = delete; + + DeviceAst(DeviceAst&& other); + DeviceAst& operator=(DeviceAst&& other); + + ~DeviceAst(); + + size_t num_nodes() const; + size_t num_functions() const; + + HostAst download() const; +}; + +#endif diff --git a/include/pareas/compiler/frontend.hpp b/include/pareas/compiler/frontend.hpp new file mode 100644 index 0000000..ec85685 --- /dev/null +++ b/include/pareas/compiler/frontend.hpp @@ -0,0 +1,82 @@ +#ifndef _PAREAS_COMPILER_FRONTEND_HPP +#define _PAREAS_COMPILER_FRONTEND_HPP + +#include "futhark_generated.h" + +#include "pareas/compiler/ast.hpp" + +#include +#include +#include + +namespace frontend { + // Keep in sync with src/compiler/frontend.fut + enum class Status : uint8_t { + OK = 0, + PARSE_ERROR = 1, + STRAY_ELSE_ERROR = 2, + INVALID_DECL = 3, + INVALID_PARAMS = 4, + INVALID_ASSIGN = 5, + INVALID_FN_PROTO = 6, + DUPLICATE_FN_OR_INVALID_CALL = 7, + INVALID_VARIABLE = 8, + INVALID_ARG_COUNT = 9, + TYPE_ERROR = 10, + INVALID_RETURN = 11, + MISSING_RETURN = 12, + }; + + const char* status_name(Status s); + + struct CompileError: std::runtime_error { + CompileError(Status status): + std::runtime_error(status_name(status)) {} + }; + + struct CombinedStatistics { + std::chrono::microseconds table_upload; + std::chrono::microseconds input_upload; + std::chrono::microseconds compile; + std::chrono::microseconds total; + + void dump(std::ostream& os) const; + }; + + DeviceAst compile_combined(futhark_context* ctx, const std::string& input, CombinedStatistics& stats); + + struct SeparateStatistics { + std::chrono::microseconds table_upload; + std::chrono::microseconds input_upload; + std::chrono::microseconds tokenize; + std::chrono::microseconds parse; + std::chrono::microseconds build_parse_tree; + std::chrono::microseconds fix_bin_ops; + std::chrono::microseconds fix_if_else; + std::chrono::microseconds flatten_lists; + std::chrono::microseconds fix_names; + std::chrono::microseconds fix_ascriptions; + std::chrono::microseconds fix_fn_decls; + std::chrono::microseconds fix_args_and_params; + std::chrono::microseconds fix_decls; + std::chrono::microseconds remove_marker_nodes; + std::chrono::microseconds compute_prev_siblings; + std::chrono::microseconds check_assignments; + std::chrono::microseconds insert_derefs; + std::chrono::microseconds extract_lexemes; + std::chrono::microseconds resolve_vars; + std::chrono::microseconds resolve_fns; + std::chrono::microseconds resolve_args; + std::chrono::microseconds resolve_data_types; + std::chrono::microseconds check_return_types; + std::chrono::microseconds check_convergence; + std::chrono::microseconds build_ast; + std::chrono::microseconds total; + + void dump(std::ostream& os) const; + }; + + DeviceAst compile_separate(futhark_context* ctx, const std::string& input, SeparateStatistics& stats); +} + +#endif diff --git a/include/pareas/compiler/futhark_interop.hpp b/include/pareas/compiler/futhark_interop.hpp index d9895af..003c831 100644 --- a/include/pareas/compiler/futhark_interop.hpp +++ b/include/pareas/compiler/futhark_interop.hpp @@ -38,9 +38,6 @@ namespace futhark { } struct Error: std::runtime_error { - Error(Context& ctx): - std::runtime_error(get_error_str(ctx.get())) {} - Error(futhark_context* ctx): std::runtime_error(get_error_str(ctx)) {} }; @@ -50,12 +47,12 @@ namespace futhark { futhark_context* ctx; Array* data; - UniqueOpaqueArray(Context& ctx, Array* data): - ctx(ctx.get()), data(data) { + UniqueOpaqueArray(futhark_context* ctx, Array* data): + ctx(ctx), data(data) { } - explicit UniqueOpaqueArray(Context& ctx): - ctx(ctx.get()), data(nullptr) { + explicit UniqueOpaqueArray(futhark_context* ctx): + ctx(ctx), data(nullptr) { } UniqueOpaqueArray(UniqueOpaqueArray&& other): @@ -87,11 +84,16 @@ namespace futhark { Array** operator&() { return &this->data; } + + Array* exchange(Array* other) { + return std::exchange(this->data, other); + } }; using UniqueLexTable = UniqueOpaqueArray; using UniqueParseTable = UniqueOpaqueArray; using UniqueStackChangeTable = UniqueOpaqueArray; + using UniqueTokenArray = UniqueOpaqueArray; template struct ArrayTraits; @@ -102,17 +104,17 @@ namespace futhark { UniqueOpaqueArray::free_fn> handle; - UniqueArray(Context& ctx, Array* data): + UniqueArray(futhark_context* ctx, Array* data): handle(ctx, data) { } - explicit UniqueArray(Context& ctx): + explicit UniqueArray(futhark_context* ctx): handle(ctx, nullptr) { } template - UniqueArray(Context& ctx, const T* data, Sizes... dims): - handle(ctx, ArrayTraits::new_fn(ctx.get(), data, dims...)) { + UniqueArray(futhark_context* ctx, const T* data, Sizes... dims): + handle(ctx, ArrayTraits::new_fn(ctx, data, dims...)) { if (!this->handle.data) throw Error(this->handle.ctx); } @@ -129,6 +131,10 @@ namespace futhark { return &this->handle; } + Array* exchange(Array* other) { + return this->handle.exchange(other); + } + void values(T* out) const { int err = ArrayTraits::values_fn(this->handle.ctx, this->handle.data, out); if (err != 0) diff --git a/meson.build b/meson.build index f2225f3..4edcd43 100644 --- a/meson.build +++ b/meson.build @@ -141,6 +141,8 @@ futhark_generated = custom_target( sources = [ 'src/compiler/main.cpp', + 'src/compiler/frontend.cpp', + 'src/compiler/ast.cpp', ] executable( diff --git a/src/compiler/ast.cpp b/src/compiler/ast.cpp new file mode 100644 index 0000000..675d85f --- /dev/null +++ b/src/compiler/ast.cpp @@ -0,0 +1,189 @@ +#include "pareas/compiler/ast.hpp" +#include "pareas/compiler/futhark_interop.hpp" + +#include +#include + +#include + +const char* data_type_name(DataType dt) { + switch (dt) { + case DataType::INVALID: return "invalid"; + case DataType::VOID: return "void"; + case DataType::INT: return "int"; + case DataType::FLOAT: return "float"; + case DataType::INT_REF: return "int ref"; + case DataType::FLOAT_REF: return "float ref"; + } +} + +void HostAst::dump_dot(std::ostream& os) const { + fmt::print(os, "digraph prog {{\n"); + + for (size_t i = 0; i < this->num_nodes; ++i) { + auto prod = this->node_types[i]; + auto parent = this->parents[i]; + auto* name = grammar::production_name(prod); + + if (parent != i) { + fmt::print( + os, + "node{} [label=\"{}\nindex={}\ndepth={}\nchild index={}", + i, + name, + i, + this->node_depths[i], + this->child_indexes[i] + ); + + switch (prod) { + case grammar::Production::ATOM_NAME: + case grammar::Production::ATOM_DECL: + case grammar::Production::ATOM_DECL_EXPLICIT: + fmt::print(os, "\\n(offset={})", this->node_data[i]); + break; + case grammar::Production::FN_DECL: + fmt::print(os, "\\n(num locals={})", this->fn_tab[this->node_data[i]]); + case grammar::Production::ATOM_FN_CALL: + fmt::print(os, "\\n(fn id={})", this->node_data[i]); + break; + case grammar::Production::PARAM: + case grammar::Production::ARG: + fmt::print(os, "\\n(arg id={})", this->node_data[i]); + break; + case grammar::Production::ATOM_INT: + fmt::print(os, "\\n(value={})", this->node_data[i]); + break; + case grammar::Production::ATOM_FLOAT: + fmt::print(os, "\\n(value={})", *reinterpret_cast(&this->node_data[i])); + break; + default: + if (this->node_data[i] != 0) { + fmt::print(os, "\\n(junk={})", this->node_data[i]); + } + } + + fmt::print(os, "\\n[{}]", data_type_name(this->data_types[i])); + fmt::print(os, "\"]\n"); + + if (parent >= 0) { + fmt::print(os, "node{} -> node{};\n", parent, i); + } else { + fmt::print(os, "start{0} [style=invis];\nstart{0} -> node{0};\n", i); + } + } + } + + fmt::print(os, "}}\n"); +} + + +DeviceAst::DeviceAst(futhark_context* ctx): + ctx(ctx), + node_types(nullptr), + parents(nullptr), + node_data(nullptr), + data_types(nullptr), + node_depths(nullptr), + child_indexes(nullptr), + fn_tab(nullptr) { +} + +DeviceAst::DeviceAst(DeviceAst&& other): + ctx(std::exchange(other.ctx, nullptr)), + node_types(std::exchange(other.node_types, nullptr)), + parents(std::exchange(other.parents, nullptr)), + node_data(std::exchange(other.node_data, nullptr)), + data_types(std::exchange(other.data_types, nullptr)), + node_depths(std::exchange(other.node_depths, nullptr)), + child_indexes(std::exchange(other.child_indexes, nullptr)), + fn_tab(std::exchange(other.fn_tab, nullptr)) { +} + +DeviceAst& DeviceAst::operator=(DeviceAst&& other) { + std::swap(this->ctx, other.ctx); + std::swap(this->node_types, other.node_types); + std::swap(this->parents, other.parents); + std::swap(this->node_data, other.node_data); + std::swap(this->data_types, other.data_types); + std::swap(this->node_depths, other.node_depths); + std::swap(this->child_indexes, other.child_indexes); + std::swap(this->fn_tab, other.fn_tab); + return *this; +} + +DeviceAst::~DeviceAst() { + if (!this->ctx) + return; + + if (this->node_types) + futhark_free_u8_1d(this->ctx, this->node_types); + + if (this->parents) + futhark_free_i32_1d(this->ctx, this->parents); + + if (this->node_data) + futhark_free_u32_1d(this->ctx, this->node_data); + + if (this->data_types) + futhark_free_u8_1d(this->ctx, this->data_types); + + if (this->node_depths) + futhark_free_i32_1d(this->ctx, this->node_depths); + + if (this->child_indexes) + futhark_free_i32_1d(this->ctx, this->child_indexes); + + if (this->fn_tab) + futhark_free_i32_1d(this->ctx, this->fn_tab); +} + +size_t DeviceAst::num_nodes() const { + return futhark_shape_u8_1d(this->ctx, this->node_types)[0]; +} + +size_t DeviceAst::num_functions() const { + return futhark_shape_i32_1d(this->ctx, this->fn_tab)[0]; +} + +HostAst DeviceAst::download() const { + size_t num_nodes = futhark_shape_u8_1d(this->ctx, this->node_types)[0]; + size_t num_functions = futhark_shape_i32_1d(this->ctx, this->fn_tab)[0]; + + auto ast = HostAst{ + .num_nodes = num_nodes, + .num_functions = num_functions, + .node_types = std::make_unique(num_nodes), + .parents = std::make_unique(num_nodes), + .node_data = std::make_unique(num_nodes), + .data_types = std::make_unique(num_nodes), + .node_depths = std::make_unique(num_nodes), + .child_indexes = std::make_unique(num_nodes), + .fn_tab = std::make_unique(num_functions) + }; + + int err = futhark_values_u8_1d( + this->ctx, + this->node_types, + reinterpret_cast*>(ast.node_types.get()) + ); + + err |= futhark_values_i32_1d(this->ctx, this->parents, ast.parents.get()); + err |= futhark_values_u32_1d(this->ctx, this->node_data, ast.node_data.get()); + + err |= futhark_values_u8_1d( + this->ctx, + this->data_types, + reinterpret_cast*>(ast.data_types.get()) + ); + + err |= futhark_values_i32_1d(this->ctx, this->node_depths, ast.node_depths.get()); + err |= futhark_values_i32_1d(this->ctx, this->child_indexes, ast.child_indexes.get()); + + err |= futhark_values_i32_1d(this->ctx, this->fn_tab, ast.fn_tab.get()); + + if (err) + throw futhark::Error(this->ctx); + + return ast; +} \ No newline at end of file diff --git a/src/compiler/frontend.cpp b/src/compiler/frontend.cpp new file mode 100644 index 0000000..efe7ab5 --- /dev/null +++ b/src/compiler/frontend.cpp @@ -0,0 +1,507 @@ +#include "pareas/compiler/frontend.hpp" +#include "pareas/compiler/futhark_interop.hpp" + +#include "pareas_grammar.hpp" + +#include +#include + +namespace { + futhark::UniqueLexTable upload_lex_table(futhark_context* ctx) { + auto initial_state = futhark::UniqueArray( + ctx, + reinterpret_cast(grammar::lex_table.initial_states), + grammar::LexTable::NUM_INITIAL_STATES + ); + + auto merge_table = futhark::UniqueArray( + ctx, + reinterpret_cast(grammar::lex_table.merge_table), + grammar::lex_table.n, + grammar::lex_table.n + ); + + auto final_state = futhark::UniqueArray( + ctx, + reinterpret_cast*>(grammar::lex_table.final_states), + grammar::lex_table.n + ); + + auto lex_table = futhark::UniqueLexTable(ctx); + + int err = futhark_entry_mk_lex_table( + ctx, + &lex_table, + initial_state.get(), + merge_table.get(), + final_state.get() + ); + + if (err) + throw futhark::Error(ctx); + + return lex_table; + } + + template + T upload_strtab(futhark_context* ctx, const grammar::StrTab& strtab, F upload_fn) { + static_assert(sizeof(U) == sizeof(uint8_t)); + + auto table = futhark::UniqueArray( + ctx, + reinterpret_cast(strtab.table), + strtab.n + ); + + auto offsets = futhark::UniqueArray(ctx, strtab.offsets, grammar::NUM_TOKENS, grammar::NUM_TOKENS); + auto lengths = futhark::UniqueArray(ctx, strtab.lengths, grammar::NUM_TOKENS, grammar::NUM_TOKENS); + + auto tab = T(ctx); + + int err = upload_fn(ctx, &tab, table.get(), offsets.get(), lengths.get()); + if (err) + throw futhark::Error(ctx); + + return tab; + } + + struct Timer { + using Clock = std::chrono::high_resolution_clock; + + futhark_context* ctx; + Clock::time_point start; + + Timer(futhark_context* ctx): + ctx(ctx) {} + + void reset() { + if (futhark_context_sync(ctx)) + throw futhark::Error(ctx); + + this->start = Clock::now(); + } + + std::chrono::microseconds lap() { + if (futhark_context_sync(ctx)) + throw futhark::Error(ctx); + + auto end = Clock::now(); + auto dif = end - this->start; + this->start = end; + return std::chrono::duration_cast(dif); + } + }; +} + +namespace frontend { + const char* status_name(Status s) { + switch (s) { + case Status::OK: return "ok"; + case Status::PARSE_ERROR: return "Parse error"; + case Status::STRAY_ELSE_ERROR: return "Stray else/elif"; + case Status::INVALID_DECL: return "Declaration cannot be both function and variable"; + case Status::INVALID_PARAMS: return "Invalid function parameter list"; + case Status::INVALID_ASSIGN: return "Invalid assignment lvalue"; + case Status::INVALID_FN_PROTO: return "Invalid function prototype"; + case Status::DUPLICATE_FN_OR_INVALID_CALL: return "Duplicate function declaration or call to undefined function"; + case Status::INVALID_VARIABLE: return "Undeclared variable"; + case Status::INVALID_ARG_COUNT: return "Invalid amount of arguments for function call"; + case Status::TYPE_ERROR: return "Type error"; + case Status::INVALID_RETURN: return "Return expression has invalid type"; + case Status::MISSING_RETURN: return "Not all code paths in non-void function return a value"; + } + } + + void CombinedStatistics::dump(std::ostream& os) const { + fmt::print(os, "table upload time: {}\n", this->table_upload); + fmt::print(os, "input upload time: {}\n", this->input_upload); + fmt::print(os, "compile time: {}\n", this->compile); + fmt::print(os, "total time: {}\n", this->total); + } + + DeviceAst compile_combined(futhark_context* ctx, const std::string& input, CombinedStatistics& stats) { + auto timer = Timer(ctx); + auto start = Timer::Clock::now(); + + timer.reset(); + auto lex_table = upload_lex_table(ctx); + auto sct = upload_strtab( + ctx, + grammar::stack_change_table, + futhark_entry_mk_stack_change_table + ); + + auto pt = upload_strtab( + ctx, + grammar::parse_table, + futhark_entry_mk_parse_table + ); + + auto arity_array = futhark::UniqueArray(ctx, grammar::arities, grammar::NUM_PRODUCTIONS); + stats.table_upload = timer.lap(); + + auto input_array = futhark::UniqueArray(ctx, reinterpret_cast(input.data()), input.size()); + stats.input_upload = timer.lap(); + + auto ast = DeviceAst(ctx); + Status status; + + int err = futhark_entry_main( + ctx, + reinterpret_cast*>(&status), + &ast.node_types, + &ast.parents, + &ast.node_data, + &ast.data_types, + &ast.node_depths, + &ast.child_indexes, + &ast.fn_tab, + input_array.get(), + lex_table.get(), + sct.get(), + pt.get(), + arity_array.get() + ); + + if (err) + throw futhark::Error(ctx); + + if (status != Status::OK) + throw CompileError(status); + + stats.compile = timer.lap(); + + auto stop = Timer::Clock::now(); + stats.total = std::chrono::duration_cast(stop - start); + + return ast; + } + + void SeparateStatistics::dump(std::ostream& os) const { + fmt::print(os, "table upload time: {}\n", this->table_upload); + fmt::print(os, "input upload time: {}\n", this->input_upload); + fmt::print(os, "tokenize time: {}\n", this->tokenize); + fmt::print(os, "parse time: {}\n", this->parse); + fmt::print(os, "build parse tree time: {}\n", this->build_parse_tree); + fmt::print(os, "fix bin ops time: {}\n", this->fix_bin_ops); + fmt::print(os, "fix conditionals time: {}\n", this->fix_if_else); + fmt::print(os, "flatten lists time: {}\n", this->flatten_lists); + fmt::print(os, "fix names time: {}\n", this->fix_names); + fmt::print(os, "fix ascriptions time: {}\n", this->fix_ascriptions); + fmt::print(os, "fix fn decls time: {}\n", this->fix_fn_decls); + fmt::print(os, "fix args and params time: {}\n", this->fix_args_and_params); + fmt::print(os, "fix decls time: {}\n", this->fix_decls); + fmt::print(os, "remove marker nodes time: {}\n", this->remove_marker_nodes); + fmt::print(os, "compute prev siblings time: {}\n", this->compute_prev_siblings); + fmt::print(os, "check assignments time: {}\n", this->check_assignments); + fmt::print(os, "insert derefs time: {}\n", this->insert_derefs); + fmt::print(os, "extract lexemestime: {}\n", this->extract_lexemes); + fmt::print(os, "resolve vars time: {}\n", this->resolve_vars); + fmt::print(os, "resolve fns time: {}\n", this->resolve_fns); + fmt::print(os, "resolve args time: {}\n", this->resolve_args); + fmt::print(os, "resolve data types time: {}\n", this->resolve_data_types); + fmt::print(os, "check return types time: {}\n", this->check_return_types); + fmt::print(os, "check convergence time: {}\n", this->check_convergence); + fmt::print(os, "build ast time: {}\n", this->build_ast); + fmt::print(os, "total time: {}\n", this->total); + } + + DeviceAst compile_separate(futhark_context* ctx, const std::string& input, SeparateStatistics& stats) { + auto timer = Timer(ctx); + auto start = Timer::Clock::now(); + + timer.reset(); + auto lex_table = upload_lex_table(ctx); + auto sct = upload_strtab( + ctx, + grammar::stack_change_table, + futhark_entry_mk_stack_change_table + ); + + auto pt = upload_strtab( + ctx, + grammar::parse_table, + futhark_entry_mk_parse_table + ); + + auto arity_array = futhark::UniqueArray(ctx, grammar::arities, grammar::NUM_PRODUCTIONS); + stats.table_upload = timer.lap(); + + auto input_array = futhark::UniqueArray(ctx, reinterpret_cast(input.data()), input.size()); + stats.input_upload = timer.lap(); + + auto ast = DeviceAst(ctx); + + auto tokens = futhark::UniqueTokenArray(ctx); + { + int err = futhark_entry_frontend_tokenize(ctx, &tokens, input_array.get(), lex_table.get()); + if (err) + throw futhark::Error(ctx); + stats.tokenize = timer.lap(); + } + + { + bool valid = false; + int err = futhark_entry_frontend_parse(ctx, &valid, &ast.node_types, tokens.get(), sct.get(), pt.get()); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::PARSE_ERROR); + stats.parse = timer.lap(); + } + + { + int err = futhark_entry_frontend_build_parse_tree(ctx, &ast.parents, ast.node_types, arity_array.get()); + if (err) + throw futhark::Error(ctx); + stats.build_parse_tree = timer.lap(); + } + + { + auto* node_types = ast.node_types; + auto* parents = ast.parents; + int err = futhark_entry_frontend_fix_bin_ops(ctx, &ast.node_types, &ast.parents, node_types, parents); + futhark_free_u8_1d(ctx, node_types); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + stats.fix_bin_ops = timer.lap(); + } + + { + auto* node_types = ast.node_types; + auto* parents = ast.parents; + bool valid; + int err = futhark_entry_frontend_fix_if_else(ctx, &valid, &ast.node_types, &ast.parents, node_types, parents); + futhark_free_u8_1d(ctx, node_types); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::STRAY_ELSE_ERROR); + stats.fix_if_else = timer.lap(); + } + + { + auto* node_types = ast.node_types; + auto* parents = ast.parents; + int err = futhark_entry_frontend_flatten_lists(ctx, &ast.node_types, &ast.parents, node_types, parents); + futhark_free_u8_1d(ctx, node_types); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + stats.flatten_lists = timer.lap(); + } + + { + auto* node_types = ast.node_types; + auto* parents = ast.parents; + bool valid; + int err = futhark_entry_frontend_fix_names(ctx, &valid, &ast.node_types, &ast.parents, node_types, parents); + futhark_free_u8_1d(ctx, node_types); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::INVALID_DECL); + stats.fix_names = timer.lap(); + } + + { + auto* parents = ast.parents; + int err = futhark_entry_frontend_fix_ascriptions(ctx, &ast.parents, ast.node_types, parents); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + stats.fix_ascriptions = timer.lap(); + } + + { + auto* parents = ast.parents; + bool valid; + int err = futhark_entry_frontend_fix_fn_decls(ctx, &valid, &ast.parents, ast.node_types, parents); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::INVALID_FN_PROTO); + stats.fix_fn_decls = timer.lap(); + } + + { + auto* node_types = ast.node_types; + int err = futhark_entry_frontend_fix_args_and_params(ctx, &ast.node_types, node_types, ast.parents); + futhark_free_u8_1d(ctx, node_types); + if (err) + throw futhark::Error(ctx); + stats.fix_args_and_params = timer.lap(); + } + + { + auto* node_types = ast.node_types; + auto* parents = ast.parents; + bool valid; + int err = futhark_entry_frontend_fix_decls(ctx, &valid, &ast.node_types, &ast.parents, node_types, parents); + futhark_free_u8_1d(ctx, node_types); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::INVALID_DECL); + stats.fix_decls = timer.lap(); + } + + { + auto* parents = ast.parents; + int err = futhark_entry_frontend_remove_marker_nodes(ctx, &ast.parents, ast.node_types, parents); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + stats.remove_marker_nodes = timer.lap(); + } + + auto prev_siblings = futhark::UniqueArray(ctx); + + { + auto* node_types = ast.node_types; + auto* parents = ast.parents; + int err = futhark_entry_frontend_compute_prev_sibling(ctx, &ast.node_types, &ast.parents, &prev_siblings, node_types, parents); + futhark_free_u8_1d(ctx, node_types); + futhark_free_i32_1d(ctx, parents); + if (err) + throw futhark::Error(ctx); + stats.compute_prev_siblings = timer.lap(); + } + + { + bool valid; + int err = futhark_entry_frontend_check_assignments(ctx, &valid, ast.node_types, ast.parents, prev_siblings.get()); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::INVALID_ASSIGN); + stats.check_assignments = timer.lap(); + } + + { + auto* node_types = ast.node_types; + auto* parents = ast.parents; + auto* old_prev_siblings = prev_siblings.exchange(nullptr); + int err = futhark_entry_frontend_insert_derefs(ctx, &ast.node_types, &ast.parents, &prev_siblings, node_types, parents, old_prev_siblings); + futhark_free_u8_1d(ctx, node_types); + futhark_free_i32_1d(ctx, parents); + futhark_free_i32_1d(ctx, old_prev_siblings); + if (err) + throw futhark::Error(ctx); + stats.insert_derefs = timer.lap(); + } + + { + int err = futhark_entry_frontend_extract_lexemes(ctx, &ast.node_data, input_array.get(), tokens.get(), ast.node_types); + if (err) + throw futhark::Error(ctx); + stats.extract_lexemes = timer.lap(); + } + + auto resolution = futhark::UniqueArray(ctx); + + { + bool valid; + int err = futhark_entry_frontend_resolve_vars(ctx, &valid, &resolution, ast.node_types, ast.parents, prev_siblings.get(), ast.node_data); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::INVALID_VARIABLE); + stats.resolve_vars = timer.lap(); + } + + { + auto* old_resolution = resolution.exchange(nullptr); + bool valid; + int err = futhark_entry_frontend_resolve_fns(ctx, &valid, &resolution, ast.node_types, old_resolution, ast.node_data); + futhark_free_i32_1d(ctx, old_resolution); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::DUPLICATE_FN_OR_INVALID_CALL); + stats.resolve_fns = timer.lap(); + } + + { + auto* old_resolution = resolution.exchange(nullptr); + bool valid; + int err = futhark_entry_frontend_resolve_args(ctx, &valid, &resolution, ast.node_types, ast.parents, prev_siblings.get(), old_resolution); + futhark_free_i32_1d(ctx, old_resolution); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::INVALID_ARG_COUNT); + stats.resolve_args = timer.lap(); + } + + { + bool valid; + int err = futhark_entry_frontend_resolve_data_types(ctx, &valid, &ast.data_types, ast.node_types, ast.parents, prev_siblings.get(), resolution.get()); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::TYPE_ERROR); + stats.resolve_data_types = timer.lap(); + } + + { + bool valid; + int err = futhark_entry_frontend_check_return_types(ctx, &valid, ast.node_types, ast.parents, ast.data_types); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::INVALID_RETURN); + stats.check_return_types = timer.lap(); + } + + { + bool valid; + int err = futhark_entry_frontend_check_convergence(ctx, &valid, ast.node_types, ast.parents, prev_siblings.get(), ast.data_types); + if (err) + throw futhark::Error(ctx); + if (!valid) + throw CompileError(Status::INVALID_RETURN); + stats.check_convergence = timer.lap(); + } + + { + auto* node_types = ast.node_types; + auto* parents = ast.parents; + auto* node_data = ast.node_data; + auto* data_types = ast.data_types; + int err = futhark_entry_frontend_build_ast( + ctx, + &ast.node_types, + &ast.parents, + &ast.node_data, + &ast.data_types, + &ast.node_depths, + &ast.child_indexes, + &ast.fn_tab, + node_types, + parents, + node_data, + data_types, + prev_siblings.get(), + resolution.get() + ); + futhark_free_u8_1d(ctx, node_types); + futhark_free_i32_1d(ctx, parents); + futhark_free_u32_1d(ctx, node_data); + futhark_free_u8_1d(ctx, data_types); + if (err) + throw futhark::Error(ctx); + stats.build_ast = timer.lap(); + } + + auto stop = Timer::Clock::now(); + stats.total = std::chrono::duration_cast(stop - start); + + return ast; + } +} diff --git a/src/compiler/frontend.fut b/src/compiler/frontend.fut index caabc34..02bc408 100644 --- a/src/compiler/frontend.fut +++ b/src/compiler/frontend.fut @@ -58,13 +58,129 @@ let status_type_error: status_code = 10 let status_invalid_return: status_code = 11 let status_missing_return: status_code = 12 +type token = (token.t, i32, i32) +entry frontend_tokenize (input: []u8) (lt: lex_table []): []token = + tokenize input lt + +entry frontend_parse (tokens: []token) (sct: stack_change_table []) (pt: parse_table []): (bool, []production.t) = + let token_types = map (.0) tokens + in if pareas_parser.check token_types sct + then (true, pareas_parser.parse token_types pt) + else (false, []) + +entry frontend_build_parse_tree [n] (node_types: [n]production.t) (arities: arity_array): [n]i32 = + pareas_parser.build_parent_vector node_types arities + +entry frontend_fix_bin_ops [n] (node_types: *[n]production.t) (parents: *[n]i32): ([]production.t, []i32) = + let (node_types, parents) = fix_bin_ops node_types parents + let (parents, old_index) = compactify parents |> unzip + let node_types = gather node_types old_index + in (node_types, parents) + +entry frontend_fix_if_else [n] (node_types: *[n]production.t) (parents: *[n]i32): (bool, [n]production.t, [n]i32) = + fix_if_else node_types parents + +entry frontend_flatten_lists [n] (node_types: *[n]production.t) (parents: *[n]i32): ([n]production.t, [n]i32) = + flatten_lists node_types parents + +entry frontend_fix_names [n] (node_types: *[n]production.t) (parents: *[n]i32): (bool, [n]production.t, [n]i32) = + fix_names node_types parents + +entry frontend_fix_ascriptions [n] (node_types: [n]production.t) (parents: *[n]i32): [n]i32 = + fix_ascriptions node_types parents + +entry frontend_fix_fn_decls [n] (node_types: [n]production.t) (parents: *[n]i32): (bool, [n]i32) = + fix_fn_decls node_types parents + +entry frontend_fix_args_and_params [n] (node_types: *[n]production.t) (parents: [n]i32): [n]production.t = + let node_types = reinsert_arg_lists node_types + let node_types = fix_param_lists node_types parents + in node_types + +entry frontend_fix_decls [n] (node_types: *[n]production.t) (parents: *[n]i32): (bool, [n]production.t, [n]i32) = + let node_types = fix_param_lists node_types parents + let valid = check_fn_params node_types parents + let (node_types, parents) = squish_decl_ascripts node_types parents + in (valid, node_types, parents) + +entry frontend_remove_marker_nodes [n] (node_types: [n]production.t) (parents: *[n]i32): [n]i32 = + remove_marker_nodes node_types parents + +entry frontend_compute_prev_sibling [n] (node_types: *[n]production.t) (parents: *[n]i32): ([]production.t, []i32, []i32) = + let (parents, old_index) = compactify parents |> unzip + let node_types = gather node_types old_index + let depths = compute_depths parents + let prev_siblings = build_sibling_vector parents depths + in (node_types, parents, prev_siblings) + +entry frontend_check_assignments [n] (node_types: [n]production.t) (parents: [n]i32) (prev_siblings: [n]i32): bool = + check_assignments node_types parents prev_siblings + +entry frontend_insert_derefs [n] (node_types: *[n]production.t) (parents: *[n]i32) (prev_siblings: *[n]i32) + : ([]production.t, []i32, []i32) + = + insert_derefs node_types parents prev_siblings |> unzip3 + +entry frontend_extract_lexemes [n] (input: []u8) (tokens: []token) (node_types: [n]production.t): [n]u32 = + build_data_vector node_types input tokens + +entry frontend_resolve_vars [n] (node_types: [n]production.t) (parents: [n]i32) (prev_siblings: [n]i32) (data: [n]u32): (bool, [n]i32) = + let right_leafs = build_right_leaf_vector parents prev_siblings + in resolve_vars node_types parents prev_siblings right_leafs data + +entry frontend_resolve_fns [n] (node_types: [n]production.t) (resolution: *[n]i32) (data: [n]u32): (bool, [n]i32) = + let (valid, fn_resolution) = resolve_fns node_types data + -- This works because declarations and function calls are disjoint. + let resolution = merge_resolutions resolution fn_resolution + in (valid, resolution) + +entry frontend_resolve_args [n] (node_types: [n]production.t) (parents: [n]i32) (prev_siblings: [n]i32) (resolution: *[n]i32): (bool, [n]i32) = + let (valid, arg_resolution) = resolve_args node_types parents prev_siblings resolution + -- This works because declarations, function calls, and function arg wrappers are disjoint. + let resolution = merge_resolutions resolution arg_resolution + in (valid, resolution) + +entry frontend_resolve_data_types [n] (node_types: [n]production.t) (parents: [n]i32) (prev_siblings: [n]i32) (resolution: [n]i32): (bool, [n]data_type.t) = + let data_types = resolve_types node_types parents prev_siblings resolution + let types_valid = check_types node_types parents prev_siblings data_types + in (types_valid, data_types) + +entry frontend_check_return_types [n] (node_types: [n]production.t) (parents: [n]i32) (data_types: [n]data_type): bool = + check_return_types node_types parents data_types + +entry frontend_check_convergence [n] (node_types: [n]production.t) (parents: [n]i32) (prev_siblings: [n]i32) (data_types: [n]data_type): bool = + check_return_paths node_types parents prev_siblings data_types + +entry frontend_build_ast [n] + (node_types: *[n]production.t) + (parents: *[n]i32) + (data: *[n]u32) + (data_types: *[n]data_type) + (prev_siblings: *[n]i32) + (resolution: *[n]i32) + : ([]production.t, []i32, []u32, []data_type, []i32, []i32, []i32) + = + let (data, fn_tab) = assign_ids node_types resolution data_types data + -- Compute the child index from the parent + let child_indexes = compute_depths prev_siblings + let left_leafs = build_left_leaf_vector parents prev_siblings + let (parents, old_index) = build_postorder_ordering parents prev_siblings left_leafs + -- Note: prev_siblings, right_leafs, left_leafs and resolution invalid from here. + let node_types = gather node_types old_index + let data = gather data old_index + let data_types = gather data_types old_index + let child_indexes = gather child_indexes old_index + -- Re-compute the depths + let depths = compute_depths parents + in (node_types, parents, data, data_types, depths, child_indexes, fn_tab) + entry main (input: []u8) (lt: lex_table []) (sct: stack_change_table []) (pt: parse_table []) (arities: arity_array) - : (status_code, []token.t, []i32, []u32, []data_type, []i32, []i32, []i32) + : (status_code, []production.t, []i32, []u32, []data_type, []i32, []i32, []i32) = let mk_error (code: status_code) = (code, [], [], [], [], [], [], []) let tokens = tokenize input lt @@ -86,10 +202,10 @@ entry main in if !valid then mk_error status_invalid_decl else let parents = fix_ascriptions node_types parents - let node_types = reinsert_arg_lists node_types let (valid, parents) = fix_fn_decls node_types parents in if !valid then mk_error status_invalid_fn_proto else + let node_types = reinsert_arg_lists node_types let node_types = fix_param_lists node_types parents let valid = check_fn_params node_types parents -- only check for validity after squish so that futhark can better merge these passes. diff --git a/src/compiler/main.cpp b/src/compiler/main.cpp index 51f3ec1..a6cf845 100644 --- a/src/compiler/main.cpp +++ b/src/compiler/main.cpp @@ -2,6 +2,8 @@ #include "pareas_grammar.hpp" #include "pareas/compiler/futhark_interop.hpp" +#include "pareas/compiler/ast.hpp" +#include "pareas/compiler/frontend.hpp" #include #include @@ -18,62 +20,6 @@ #include #include -// Keep in sync with src/compiler/frontend.fut -enum class Status : uint8_t { - OK = 0, - PARSE_ERROR = 1, - STRAY_ELSE_ERROR = 2, - INVALID_DECL = 3, - INVALID_PARAMS = 4, - INVALID_ASSIGN = 5, - INVALID_FN_PROTO = 6, - DUPLICATE_FN_OR_INVALID_CALL = 7, - INVALID_VARIABLE = 8, - INVALID_ARG_COUNT = 9, - TYPE_ERROR = 10, - INVALID_RETURN = 11, - MISSING_RETURN = 12, -}; - -const char* status_name(Status s) { - switch (s) { - case Status::OK: return "ok"; - case Status::PARSE_ERROR: return "parse error"; - case Status::STRAY_ELSE_ERROR: return "stray else/elif"; - case Status::INVALID_DECL: return "Declaration cannot be both function and variable"; - case Status::INVALID_PARAMS: return "Invalid function parameter list"; - case Status::INVALID_ASSIGN: return "Invalid assignment lvalue"; - case Status::INVALID_FN_PROTO: return "Invalid function prototype"; - case Status::DUPLICATE_FN_OR_INVALID_CALL: return "Duplicate function declaration or call to undefined function"; - case Status::INVALID_VARIABLE: return "Undeclared variable"; - case Status::INVALID_ARG_COUNT: return "Invalid amount of arguments for function call"; - case Status::TYPE_ERROR: return "Type error"; - case Status::INVALID_RETURN: return "Return expression has invalid type"; - case Status::MISSING_RETURN: return "Not all code paths in non-void function return a value"; - } -} - -// Keep in sync with src/compiler/datanode_types.fut -enum class DataType : uint8_t { - INVALID = 0, - VOID = 1, - INT = 2, - FLOAT = 3, - INT_REF = 4, - FLOAT_REF = 5, -}; - -const char* data_type_name(DataType dt) { - switch (dt) { - case DataType::INVALID: return "invalid"; - case DataType::VOID: return "void"; - case DataType::INT: return "int"; - case DataType::FLOAT: return "float"; - case DataType::INT_REF: return "int ref"; - case DataType::FLOAT_REF: return "float ref"; - } -} - struct Options { const char* input_path; const char* output_path; @@ -81,6 +27,7 @@ struct Options { bool verbose; bool debug; bool dump_dot; + bool benchmark; // Options available for the multicore backend int threads; @@ -99,6 +46,7 @@ void print_usage(char* progname) { "-v --verbose Enable Futhark logging.\n" "-d --debug Enable Futhark debug logging.\n" "--dump-dot Dump tree as dot graph.\n" + "--benchmark Record benchmark information.\n" #if defined(FUTHARK_BACKEND_multicore) "Available backend options:\n" "-t --threads Set the maximum number of threads that may be used\n" @@ -175,6 +123,8 @@ bool parse_options(Options* opts, int argc, char* argv[]) { opts->debug = true; } else if (arg == "--dump-dot") { opts->dump_dot = true; + } else if (arg == "--benchmark") { + opts->benchmark = true; } else if (!opts->input_path) { opts->input_path = argv[i]; } else { @@ -221,160 +171,6 @@ struct Free { template using MallocPtr = std::unique_ptr>; -futhark::UniqueLexTable upload_lex_table(futhark::Context& ctx) { - auto initial_state = futhark::UniqueArray( - ctx, - reinterpret_cast(grammar::lex_table.initial_states), - grammar::LexTable::NUM_INITIAL_STATES - ); - - auto merge_table = futhark::UniqueArray( - ctx, - reinterpret_cast(grammar::lex_table.merge_table), - grammar::lex_table.n, - grammar::lex_table.n - ); - - auto final_state = futhark::UniqueArray( - ctx, - reinterpret_cast*>(grammar::lex_table.final_states), - grammar::lex_table.n - ); - - auto lex_table = futhark::UniqueLexTable(ctx); - - int err = futhark_entry_mk_lex_table( - ctx.get(), - &lex_table, - initial_state.get(), - merge_table.get(), - final_state.get() - ); - - if (err) - throw futhark::Error(ctx); - - return lex_table; -} - -template -T upload_strtab(futhark::Context& ctx, const grammar::StrTab& strtab, F upload_fn) { - static_assert(sizeof(U) == sizeof(uint8_t)); - - auto table = futhark::UniqueArray( - ctx, - reinterpret_cast(strtab.table), - strtab.n - ); - - auto offsets = futhark::UniqueArray(ctx, strtab.offsets, grammar::NUM_TOKENS, grammar::NUM_TOKENS); - auto lengths = futhark::UniqueArray(ctx, strtab.lengths, grammar::NUM_TOKENS, grammar::NUM_TOKENS); - - auto tab = T(ctx); - - int err = upload_fn(ctx.get(), &tab, table.get(), offsets.get(), lengths.get()); - if (err != 0) - throw futhark::Error(ctx); - - return tab; -} - -void render_tree( - size_t n, - const grammar::Production* node_types, - const int32_t* parents, - const uint32_t* data, - const DataType* data_types, - const int32_t* depths, - const int32_t* child_indexes, - const int32_t* fn_tab -) { - fmt::print("digraph prog {{\n"); - - for (size_t i = 0; i < n; ++i) { - auto prod = node_types[i]; - auto parent = parents[i]; - auto* name = grammar::production_name(prod); - - if (parent != i) { - fmt::print("node{} [label=\"{}\nindex={}\ndepth={}\nchild index={}", i, name, i, depths[i], child_indexes[i]); - - switch (prod) { - case grammar::Production::ATOM_NAME: - case grammar::Production::ATOM_DECL: - case grammar::Production::ATOM_DECL_EXPLICIT: - fmt::print("\\n(offset={})", data[i]); - break; - case grammar::Production::FN_DECL: - fmt::print("\\n(num locals={})", fn_tab[data[i]]); - case grammar::Production::ATOM_FN_CALL: - fmt::print("\\n(fn id={})", data[i]); - break; - case grammar::Production::PARAM: - case grammar::Production::ARG: - fmt::print("\\n(arg id={})", data[i]); - break; - case grammar::Production::ATOM_INT: - fmt::print("\\n(value={})", data[i]); - break; - case grammar::Production::ATOM_FLOAT: - fmt::print("\\n(value={})", *reinterpret_cast(&data[i])); - break; - default: - if (data[i] != 0) { - fmt::print("\\n(junk={})", data[i]); - } - } - - fmt::print("\\n[{}]", data_type_name(data_types[i])); - fmt::print("\"]\n"); - - if (parent >= 0) { - fmt::print("node{} -> node{};\n", parent, i); - } else { - fmt::print("start{0} [style=invis];\nstart{0} -> node{0};\n", i); - } - } - } - - fmt::print("}}\n"); -} - -void download_and_render_tree( - futhark::Context& ctx, - futhark::UniqueArray& node_types, - futhark::UniqueArray& parents, - futhark::UniqueArray& data, - futhark::UniqueArray& data_types, - futhark::UniqueArray& depths, - futhark::UniqueArray& child_indexes, - futhark::UniqueArray& fn_tab -) { - int64_t n = node_types.shape()[0]; - - auto host_node_types = node_types.download(); - auto host_parents = parents.download(); - auto host_data = data.download(); - auto host_data_types = data_types.download(); - auto host_depths = depths.download(); - auto host_child_indexes = child_indexes.download(); - auto host_fn_tab = fn_tab.download(); - - if (futhark_context_sync(ctx.get())) - throw futhark::Error(ctx); - - render_tree( - n, - reinterpret_cast(host_node_types.data()), - host_parents.data(), - host_data.data(), - reinterpret_cast(host_data_types.data()), - host_depths.data(), - host_child_indexes.data(), - host_fn_tab.data() - ); -} - int main(int argc, char* argv[]) { Options opts; if (!parse_options(&opts, argc, argv)) { @@ -411,94 +207,40 @@ int main(int argc, char* argv[]) { auto ctx = futhark::Context(futhark_context_new(config.get())); - auto start = std::chrono::high_resolution_clock::now(); - - auto lex_table = upload_lex_table(ctx); - - auto sct = upload_strtab( - ctx, - grammar::stack_change_table, - futhark_entry_mk_stack_change_table - ); + try { + auto ast = DeviceAst(ctx.get()); - auto pt = upload_strtab( - ctx, - grammar::parse_table, - futhark_entry_mk_parse_table - ); - - auto arity_array = futhark::UniqueArray(ctx, grammar::arities, grammar::NUM_PRODUCTIONS); - auto input_array = futhark::UniqueArray(ctx, reinterpret_cast(input.data()), input.size()); - - if (futhark_context_sync(ctx.get()) != 0) - throw futhark::Error(ctx); - - auto stop = std::chrono::high_resolution_clock::now(); - fmt::print(std::cerr, "Upload time: {}\n", std::chrono::duration_cast(stop - start)); - - auto node_types = futhark::UniqueArray(ctx); - auto parents = futhark::UniqueArray(ctx); - auto data = futhark::UniqueArray(ctx); - auto data_types = futhark::UniqueArray(ctx); - auto child_indexes = futhark::UniqueArray(ctx); - auto depths = futhark::UniqueArray(ctx); - auto fn_tab = futhark::UniqueArray(ctx); - Status status; - - start = std::chrono::high_resolution_clock::now(); - int err = futhark_entry_main( - ctx.get(), - reinterpret_cast*>(&status), - &node_types, - &parents, - &data, - &data_types, - &depths, - &child_indexes, - &fn_tab, - input_array.get(), - lex_table.get(), - sct.get(), - pt.get(), - arity_array.get() - ); - - if (err != 0) - throw futhark::Error(ctx); - - if (futhark_context_sync(ctx.get()) != 0) - throw futhark::Error(ctx); - - stop = std::chrono::high_resolution_clock::now(); - fmt::print(std::cerr, "Main kernel runtime: {}\n", std::chrono::duration_cast(stop - start)); + if (opts.benchmark) { + frontend::SeparateStatistics stats; + ast = frontend::compile_separate(ctx.get(), input, stats); + stats.dump(std::cerr); + } else { + frontend::CombinedStatistics stats; + ast = frontend::compile_combined(ctx.get(), input, stats); + stats.dump(std::cerr); + } - if (status == Status::OK) { - fmt::print(std::cerr, "{} nodes\n", node_types.shape()[0]); + fmt::print(std::cerr, "{} nodes\n", ast.num_nodes()); if (opts.dump_dot) { - download_and_render_tree( - ctx, - node_types, - parents, - data, - data_types, - depths, - child_indexes, - fn_tab - ); + auto host_ast = ast.download(); + host_ast.dump_dot(std::cout); } - } else { - fmt::print(std::cerr, "Error: {}\n", status_name(status)); - err = 1; - } - if (opts.profile) { - auto report = MallocPtr(futhark_context_report(ctx.get())); - fmt::print("Profile report:\n{}", report); - } + if (opts.profile) { + auto report = MallocPtr(futhark_context_report(ctx.get())); + fmt::print("Profile report:\n{}", report); + } - if (futhark_context_sync(ctx.get()) != 0) - throw futhark::Error(ctx); + if (futhark_context_sync(ctx.get()) != 0) + throw futhark::Error(ctx.get()); + } catch (const frontend::CompileError& err) { + fmt::print(std::cerr, "Compile error: {}\n", err.what()); + return EXIT_FAILURE; + } catch (const futhark::Error& err) { + fmt::print(std::cerr, "Futhark error: {}\n", err.what()); + return EXIT_FAILURE; + } - return !err ? EXIT_SUCCESS : EXIT_FAILURE; + return EXIT_SUCCESS; }