diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 941351397..391f74590 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -260,6 +260,11 @@ x{7F} @Defop TRUE x{83FF} @Defop PUSHNAN { > compile_asm(td::Slice asm_code) { td::Result compile_asm_program(std::string&& program_code, const std::string& fift_dir) { std::string main_fif; - main_fif.reserve(program_code.size() + 100); + main_fif.reserve(program_code.size() + 200); main_fif.append(program_code.data(), program_code.size()); main_fif.append(R"( dup hashB B>X $>B "hex" B>file)"); // write codeHashHex to a file main_fif.append(R"( boc>B B>base64 $>B "boc" B>file)"); // write codeBoc64 to a file diff --git a/tolk/CMakeLists.txt b/tolk/CMakeLists.txt index 6657be95d..3f3571021 100644 --- a/tolk/CMakeLists.txt +++ b/tolk/CMakeLists.txt @@ -31,6 +31,7 @@ set(TOLK_SOURCE pipe-ast-to-legacy.cpp pipe-find-unused-symbols.cpp pipe-generate-fif-output.cpp + pipe-generate-source-map.cpp type-system.cpp smart-casts-cfg.cpp generics-helpers.cpp @@ -43,6 +44,7 @@ set(TOLK_SOURCE stack-transform.cpp optimize.cpp codegen.cpp + debug-info.cpp tolk.cpp ) diff --git a/tolk/abscode.cpp b/tolk/abscode.cpp index 462aad7b2..cabfd84ec 100644 --- a/tolk/abscode.cpp +++ b/tolk/abscode.cpp @@ -254,6 +254,10 @@ void Op::show(std::ostream& os, const std::vector& vars, std::string pfx show_var_list(os, left, vars); os << " := " << str_const << std::endl; break; + case _DebugInfo: + os << pfx << dis << "DEBUGINFO "; + os << source_map_entry_idx << std::endl; + break; case _Import: os << pfx << dis << "IMPORT "; show_var_list(os, left, vars); @@ -394,7 +398,7 @@ void CodeBlob::print(std::ostream& os, int flags) const { os << "-------- END ---------\n\n"; } -std::vector CodeBlob::create_var(TypePtr var_type, SrcLocation loc, std::string name) { +std::vector CodeBlob::create_var(TypePtr var_type, SrcLocation loc, std::string name, TypePtr parent_type) { std::vector ir_idx; int stack_w = var_type->get_width_on_stack(); ir_idx.reserve(stack_w); @@ -402,33 +406,33 @@ std::vector CodeBlob::create_var(TypePtr var_type, SrcLocation loc, s for (int i = 0; i < t_struct->struct_ref->get_num_fields(); ++i) { StructFieldPtr field_ref = t_struct->struct_ref->get_field(i); std::string sub_name = name.empty() || t_struct->struct_ref->get_num_fields() == 1 ? name : name + "." + field_ref->name; - std::vector nested = create_var(field_ref->declared_type, loc, std::move(sub_name)); + std::vector nested = create_var(field_ref->declared_type, loc, std::move(sub_name), parent_type); ir_idx.insert(ir_idx.end(), nested.begin(), nested.end()); } } else if (const TypeDataTensor* t_tensor = var_type->try_as()) { for (int i = 0; i < t_tensor->size(); ++i) { std::string sub_name = name.empty() ? name : name + "." + std::to_string(i); - std::vector nested = create_var(t_tensor->items[i], loc, std::move(sub_name)); + std::vector nested = create_var(t_tensor->items[i], loc, std::move(sub_name), parent_type); ir_idx.insert(ir_idx.end(), nested.begin(), nested.end()); } } else if (const TypeDataAlias* t_alias = var_type->try_as()) { - ir_idx = create_var(t_alias->underlying_type, loc, std::move(name)); + ir_idx = create_var(t_alias->underlying_type, loc, std::move(name), parent_type); } else if (const TypeDataUnion* t_union = var_type->try_as(); t_union && stack_w != 1) { std::string utag_name = name.empty() ? "'UTag" : name + ".UTag"; if (t_union->or_null) { // in stack comments, `a:(int,int)?` will be "a.0 a.1 a.UTag" - ir_idx = create_var(t_union->or_null, loc, std::move(name)); + ir_idx = create_var(t_union->or_null, loc, std::move(name), parent_type); } else { // in stack comments, `a:int|slice` will be "a.USlot1 a.UTag" for (int i = 0; i < stack_w - 1; ++i) { std::string slot_name = name.empty() ? "'USlot" + std::to_string(i + 1) : name + ".USlot" + std::to_string(i + 1); - ir_idx.emplace_back(create_var(TypeDataUnknown::create(), loc, std::move(slot_name))[0]); + ir_idx.emplace_back(create_var(TypeDataUnknown::create(), loc, std::move(slot_name), var_type)[0]); } } - ir_idx.emplace_back(create_var(TypeDataInt::create(), loc, std::move(utag_name))[0]); + ir_idx.emplace_back(create_var(TypeDataInt::create(), loc, std::move(utag_name), parent_type)[0]); } else if (var_type != TypeDataVoid::create() && var_type != TypeDataNever::create()) { #ifdef TOLK_DEBUG tolk_assert(stack_w == 1); #endif - vars.emplace_back(var_cnt, var_type, std::move(name), loc); + vars.emplace_back(var_cnt, var_type, std::move(name), loc, parent_type); ir_idx.emplace_back(var_cnt); var_cnt++; } diff --git a/tolk/analyzer.cpp b/tolk/analyzer.cpp index 5d70b8064..2a49091e3 100644 --- a/tolk/analyzer.cpp +++ b/tolk/analyzer.cpp @@ -303,7 +303,7 @@ bool Op::std_compute_used_vars(bool disabled) { bool Op::compute_used_vars(const CodeBlob& code, bool edit) { tolk_assert(next); const VarDescrList& next_var_info = next->var_info; - if (cl == _Nop) { + if (cl == _Nop || cl == _DebugInfo) { return set_var_info_except(next_var_info, left); } switch (cl) { @@ -528,6 +528,7 @@ bool prune_unreachable(std::unique_ptr& ops) { case Op::_UnTuple: case Op::_Import: case Op::_Let: + case Op::_DebugInfo: reach = true; break; case Op::_Return: @@ -693,6 +694,7 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { switch (cl) { case _Nop: case _Import: + case _DebugInfo: break; case _Return: values.set_unreachable(); @@ -887,6 +889,7 @@ bool Op::mark_noreturn() { case _SetGlob: case _GlobVar: case _CallInd: + case _DebugInfo: return set_noreturn(next->mark_noreturn()); case _Return: return set_noreturn(); diff --git a/tolk/asmops.cpp b/tolk/asmops.cpp index 3dc39239b..335cfddab 100644 --- a/tolk/asmops.cpp +++ b/tolk/asmops.cpp @@ -330,6 +330,21 @@ void AsmOpList::show_var_ext(std::ostream& os, std::pair } } +std::optional> AsmOpList::get_var(const std::pair& idx_pair) const { + const var_idx_t var_idx = idx_pair.first; + const const_idx_t const_idx = idx_pair.second; + if (!var_names_ || static_cast(var_idx) >= var_names_->size()) { + return std::nullopt; + } + const auto var = var_names_->at(var_idx); + if (static_cast(const_idx) < constants_.size() && constants_[const_idx].not_null()) { + const auto value = constants_[const_idx]; + const auto value_str = value->to_dec_string(); + return std::tie(var, value_str); + } + return std::tie(var, ""); +} + void AsmOpList::out(std::ostream& os, int mode) const { std::size_t n = list_.size(); for (std::size_t i = 0; i < n; i++) { diff --git a/tolk/ast-stringifier.h b/tolk/ast-stringifier.h index 570ab048c..3cad2afc3 100644 --- a/tolk/ast-stringifier.h +++ b/tolk/ast-stringifier.h @@ -16,8 +16,6 @@ */ #pragma once -#ifdef TOLK_DEBUG - #include "ast.h" #include "ast-visitor.h" #include "type-system.h" @@ -379,5 +377,3 @@ class ASTStringifier final : public ASTVisitor { }; } // namespace tolk - -#endif // TOLK_DEBUG diff --git a/tolk/codegen.cpp b/tolk/codegen.cpp index 8be368810..9d1556b78 100644 --- a/tolk/codegen.cpp +++ b/tolk/codegen.cpp @@ -276,6 +276,29 @@ void Stack::rearrange_top(SrcLocation loc, var_idx_t top, bool last) { } bool Op::generate_code_step(Stack& stack) { + // we need to handle it here to correctly handle case `IFJMP { DROP }` + if (cl == _DebugInfo) { + std::ostringstream ops; + ops << source_map_entry_idx << " DEBUGMARK"; // pseudo instruction + + // Append opcode to a list + if (const auto list_size = stack.o.list_.size(); list_size > 0) { + stack.o.insert(stack.o.list_.size(), loc, ops.str()); + } + + if (source_map_entry_idx < G.source_map.size()) { + auto& entry = G.source_map.at(source_map_entry_idx); + + // Collect all available variables at this point + for (const auto index : stack.s) { + if (const auto var = stack.o.get_var(index); var.has_value()) { + const auto& [data, value] = *var; + entry.vars.push_back({data, value}); + } + } + } + } + stack.opt_show(); // detect `throw 123` (actually _IntConst 123 + _Call __throw) @@ -882,6 +905,10 @@ bool Op::generate_code_step(Stack& stack) { stack.o << AsmOp::Custom(loc, "TRY"); return true; } + case _DebugInfo: { + // already handled above + return true; + } default: std::cerr << "fatal: unknown operation \n"; throw ParseError(loc, "unknown operation in generate_code()"); diff --git a/tolk/compiler-state.h b/tolk/compiler-state.h index cf3a11ffa..0a5c4ba05 100644 --- a/tolk/compiler-state.h +++ b/tolk/compiler-state.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace tolk { @@ -53,9 +54,11 @@ struct CompilerSettings { int optimization_level = 2; bool stack_layout_comments = true; bool tolk_src_as_line_comments = true; + bool collect_source_map = false; std::string output_filename; std::string boc_output_filename; + std::string source_map_output_filename; std::string stdlib_folder; // path to tolk-stdlib/; note: from tolk-js it's empty! tolk-js reads files via js callback FsReadCallback read_callback; @@ -106,6 +109,8 @@ struct CompilerState { std::vector all_enums; AllRegisteredSrcFiles all_src_files; + std::vector source_map; + bool is_verbosity(int gt_eq) const { return settings.verbosity >= gt_eq; } }; diff --git a/tolk/debug-info.cpp b/tolk/debug-info.cpp new file mode 100644 index 000000000..c3534916e --- /dev/null +++ b/tolk/debug-info.cpp @@ -0,0 +1,69 @@ +#include "tolk.h" +#include +#include +#include "ast-stringifier.h" + +namespace tolk { + +void insert_debug_info(SrcLocation loc, ASTNodeKind kind, CodeBlob& code, bool is_leave, std::string descr) { + if (!G.settings.collect_source_map) { + return; + } + + if (kind == ast_artificial_aux_vertex || kind == ast_throw_statement) { + return; + } + +#ifdef TOLK_DEBUG + const auto last_op = std::find_if(code._vector_of_ops.rbegin(), code._vector_of_ops.rend(), [](const auto& it) { + return it->cl != Op::_DebugInfo; + }); + const Op* last_op_ptr = last_op != code._vector_of_ops.rend() ? *last_op : nullptr; +#endif + + auto& op = code.emplace_back(loc, Op::_DebugInfo); + op.source_map_entry_idx = G.source_map.size(); + + auto info = SourceMapEntry{}; + info.idx = op.source_map_entry_idx; + info.descr = descr; + info.is_entry = kind == ast_function_declaration; + info.is_leave = is_leave; + +#ifdef TOLK_DEBUG + if (last_op_ptr) { + std::stringstream st; + last_op_ptr->show(st, code.vars, "", 4); + + info.opcode = st.str(); + } +#endif + + info.ast_kind = ASTStringifier::ast_node_kind_to_string(kind); + + if (const SrcFile* src_file = loc.get_src_file(); src_file != nullptr) { + const auto& pos = src_file->convert_offset(loc.get_char_offset()); + + info.loc.file = src_file->realpath; + info.loc.offset = loc.get_char_offset(); + info.loc.line = pos.line_no; + info.loc.line_offset = is_leave; + info.loc.col = pos.char_no - 1; + info.loc.length = 1; // Once we have the actual length of node, we should use it here + } + + info.func_name = code.fun_ref->name; + if (code.name != info.func_name) { + // If a function was inlined, `code.name` will contain the name of the function we are inlining into + info.inlined_to_func_name = code.name; + } + info.func_inline_mode = code.fun_ref->inline_mode; + + G.source_map.push_back(info); +} + +void insert_debug_info(AnyV v, CodeBlob& code) { + insert_debug_info(v->loc, v->kind, code, 0, ""); +} + +} diff --git a/tolk/pack-unpack-api.cpp b/tolk/pack-unpack-api.cpp index f710d2e30..8d0f63255 100644 --- a/tolk/pack-unpack-api.cpp +++ b/tolk/pack-unpack-api.cpp @@ -127,7 +127,7 @@ class PackUnpackAvailabilityChecker { } return {}; } - + if (const auto* t_union = any_type->try_as()) { // a union can almost always be serialized if every of its variants can: // - `T?` is TL/B `(Maybe T)` @@ -253,6 +253,8 @@ std::vector generate_T_toCell(FunctionPtr called_f, CodeBlob& code, S FunctionPtr f_beginCell = lookup_function("beginCell"); FunctionPtr f_endCell = lookup_function("builder.endCell"); std::vector rvect_builder = code.create_var(TypeDataBuilder::create(), loc, "b"); + + insert_debug_info(loc, ast_function_call, code); code.emplace_back(loc, Op::_Call, rvect_builder, std::vector{}, f_beginCell); PackContext ctx(code, loc, rvect_builder, args[1]); @@ -267,6 +269,7 @@ std::vector generate_T_toCell(FunctionPtr called_f, CodeBlob& code, S // fun builder.storeAny(mutate self, v: T, options: PackOptions = {}): self std::vector generate_builder_storeAny(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& args) { TypePtr typeT = called_f->substitutedTs->typeT_at(0); + insert_debug_info(loc, ast_function_call, code); PackContext ctx(code, loc, args[0], args[2]); // mutate this builder ctx.generate_pack_any(typeT, std::vector(args[1])); @@ -275,6 +278,8 @@ std::vector generate_builder_storeAny(FunctionPtr called_f, CodeBlob& // fun T.fromSlice(rawSlice: slice, options: UnpackOptions): T std::vector generate_T_fromSlice(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& args) { + insert_debug_info(loc, ast_function_call, code); + std::vector slice_copy = code.create_var(TypeDataSlice::create(), loc, "s"); code.emplace_back(loc, Op::_Let, slice_copy, args[0]); @@ -305,6 +310,8 @@ std::vector generate_slice_loadAny(FunctionPtr called_f, CodeBlob& co // fun T.fromCell(packedCell: cell, options: UnpackOptions): T // fun Cell.load(self, options: UnpackOptions): T std::vector generate_T_fromCell(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& args) { + insert_debug_info(loc, ast_function_call, code); + TypePtr typeT = called_f->substitutedTs->typeT_at(0); FunctionPtr f_beginParse = lookup_function("cell.beginParse"); std::vector ir_slice = code.create_var(TypeDataSlice::create(), loc, "s"); @@ -324,6 +331,7 @@ std::vector generate_T_fromCell(FunctionPtr called_f, CodeBlob& code, // fun slice.skipAny(mutate self, options: UnpackOptions): self std::vector generate_slice_skipAny(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& args) { TypePtr typeT = called_f->substitutedTs->typeT_at(0); + insert_debug_info(loc, ast_function_call, code); UnpackContext ctx(code, loc, args[0], args[1]); // mutate this slice ctx.generate_skip_any(typeT); @@ -392,6 +400,8 @@ std::vector generate_lazy_struct_to_cell(CodeBlob& code, SrcLocation StructPtr original_struct = loaded_state->original_struct; StructPtr hidden_struct = loaded_state->hidden_struct; + insert_debug_info(loc, ast_function_call, code); + std::vector rvect_builder = code.create_var(TypeDataBuilder::create(), loc, "b"); code.emplace_back(loc, Op::_Call, rvect_builder, std::vector{}, lookup_function("beginCell")); diff --git a/tolk/pack-unpack-serializers.cpp b/tolk/pack-unpack-serializers.cpp index d000bfb6f..6bd9551ec 100644 --- a/tolk/pack-unpack-serializers.cpp +++ b/tolk/pack-unpack-serializers.cpp @@ -725,12 +725,14 @@ struct S_Either final : ISerializer { } } tolk_assert(options.match_blocks.size() == 2); + insert_debug_info(loc, ast_match_expression, code); std::vector ir_result = code.create_tmp_var(options.match_expr_type, loc, "(match-expression)"); std::vector ir_is_right = ctx->loadUint(1, "(eitherBit)"); Op& if_op = code.emplace_back(loc, Op::_If, std::move(ir_is_right)); { code.push_set_cur(if_op.block0); const LazyMatchOptions::MatchBlock* m_block = options.find_match_block(t_right); + insert_debug_info(m_block->v_body->loc, ast_match_arm, code); std::vector ith_result = pre_compile_expr(m_block->v_body, code); options.save_match_result_on_arm_end(code, loc, m_block, std::move(ith_result), ir_result); code.close_pop_cur(loc); @@ -738,6 +740,7 @@ struct S_Either final : ISerializer { { code.push_set_cur(if_op.block1); const LazyMatchOptions::MatchBlock* m_block = options.find_match_block(t_left); + insert_debug_info(m_block->v_body->loc, ast_match_arm, code); std::vector ith_result = pre_compile_expr(m_block->v_body, code); options.save_match_result_on_arm_end(code, loc, m_block, std::move(ith_result), ir_result); code.close_pop_cur(loc); @@ -848,8 +851,10 @@ struct S_MultipleConstructors final : ISerializer { std::vector ir_prefix_eq = code.create_tmp_var(TypeDataInt::create(), loc, "(prefix-eq)"); for (int i = 0; i < t_union->size(); ++i) { + const LazyMatchOptions::MatchBlock* m_block = options.find_match_block(t_union->variants[i]); StructData::PackOpcode opcode = opcodes[opcodes_order_mapping[i]]; std::vector args = { ctx->ir_slice0, code.create_int(loc, opcode.pack_prefix, "(pack-prefix)"), code.create_int(loc, opcode.prefix_len, "(prefix-len)") }; + insert_debug_info(m_block->arm_variant_node->loc, ast_match_arm, code); code.emplace_back(loc, Op::_Call, std::vector{ctx->ir_slice0, ir_prefix_eq[0]}, std::move(args), f_tryStripPrefix); Op& if_op = code.emplace_back(loc, Op::_If, ir_prefix_eq); code.push_set_cur(if_op.block0); @@ -1004,6 +1009,8 @@ struct S_CustomStruct final : ISerializer { std::vector ir_result = code.create_tmp_var(options.match_expr_type, loc, "(match-expression)"); std::vector ir_prefix_eq = code.create_tmp_var(TypeDataInt::create(), loc, "(prefix-eq)"); + insert_debug_info(loc, ast_match_arm, code); + StructData::PackOpcode opcode = struct_ref->opcode; if (opcode.exists()) { // it's `match` over a struct (makes sense for a struct with prefix and `else` branch) std::vector args = { ctx->ir_slice0, code.create_int(loc, opcode.pack_prefix, "(pack-prefix)"), code.create_int(loc, opcode.prefix_len, "(prefix-len)") }; diff --git a/tolk/pack-unpack-serializers.h b/tolk/pack-unpack-serializers.h index a511f751e..99d74301b 100644 --- a/tolk/pack-unpack-serializers.h +++ b/tolk/pack-unpack-serializers.h @@ -94,6 +94,7 @@ enum class PrefixReadMode { struct LazyMatchOptions { struct MatchBlock { TypePtr arm_variant; // left of `V => ...`; nullptr for `else => ...` + AnyV arm_variant_node; // left of `V => ...` as node for debug info; nullptr for `else => ...` AnyExprV v_body; // right of `V => ...` TypePtr block_expr_type; // for match expression, if `V => expr`, it's expr's inferred_type }; diff --git a/tolk/pipe-ast-to-legacy.cpp b/tolk/pipe-ast-to-legacy.cpp index 568f4868d..01c2ebe08 100644 --- a/tolk/pipe-ast-to-legacy.cpp +++ b/tolk/pipe-ast-to-legacy.cpp @@ -24,6 +24,9 @@ #include "smart-casts-cfg.h" #include "pack-unpack-api.h" #include "gen-entrypoints.h" +#include "generics-helpers.h" +#include "gen-entrypoints.h" +#include /* * This pipe is the last one operating AST: it transforms AST to IR. @@ -609,6 +612,8 @@ std::vector pre_compile_is_type(CodeBlob& code, TypePtr expr_type, Ty static std::vector gen_op_call(CodeBlob& code, TypePtr ret_type, SrcLocation loc, std::vector&& args_vars, FunctionPtr fun_ref, const char* debug_desc, bool arg_order_already_equals_asm = false) { + insert_debug_info(loc, ast_function_call, code); + std::vector rvect = code.create_tmp_var(ret_type, loc, debug_desc); Op& op = code.emplace_back(loc, Op::_Call, rvect, std::move(args_vars), fun_ref); if (!fun_ref->is_marked_as_pure()) { @@ -624,6 +629,9 @@ static std::vector gen_compile_time_code_instead_of_fun_call(CodeBlob SrcLocation loc = v_call->loc; FunctionPtr called_f = v_call->fun_maybe; + // Mark compile-time call site explicitly + insert_debug_info(loc, ast_function_call, code); + if (called_f->is_method() && called_f->is_instantiation_of_generic_function()) { std::string_view f_name = called_f->base_fun_ref->name; const LazyVariableLoadedState* lazy_variable = v_call->dot_obj_is_self ? code.get_lazy_variable(v_call->get_self_obj()) : nullptr; @@ -649,6 +657,14 @@ static std::vector gen_compile_time_code_instead_of_fun_call(CodeBlob } std::vector gen_inline_fun_call_in_place(CodeBlob& code, TypePtr ret_type, SrcLocation loc, FunctionPtr f_inlined, AnyExprV self_obj, bool is_before_immediate_return, const std::vector>& vars_per_arg) { + insert_debug_info(loc, ast_function_call, code); + if (G.settings.collect_source_map && G.source_map.size() > 0) { + // Inlined functions are tricky for handling in debuggers, code coverage and other tools + // which uses source maps. To simplify handling we explicitly mark start and end instructions + // of inlined function, so tools can understand when we step into and step out inlined function. + G.source_map.at(G.source_map.size() - 1).before_inlined_function_call = true; + } + tolk_assert(vars_per_arg.size() == f_inlined->parameters.size()); for (int i = 0; i < f_inlined->get_num_params(); ++i) { const LocalVarData& param_i = f_inlined->get_param(i); @@ -697,6 +713,12 @@ std::vector gen_inline_fun_call_in_place(CodeBlob& code, TypePtr ret_ ClearStateAfterInlineInPlace visitor; visitor.start_visiting_function(f_inlined, v_ast_root); + insert_debug_info(loc, ast_function_call, code); + if (G.settings.collect_source_map && G.source_map.size() > 0) { + // Mark end instruction as well + G.source_map.at(G.source_map.size() - 1).after_inlined_function_call = true; + } + code.fun_ref = backup_cur_fun; code.inline_rvect_out = backup_outer_inline; code.inlining_before_immediate_return = backup_inline_before_return; @@ -1160,7 +1182,7 @@ std::vector pre_compile_symbol(SrcLocation loc, const Symbol* sym, Co } std::vector ir_sub_const = pre_compile_expr(const_ref->init_value, code, const_ref->declared_type); code.cached_consts.emplace_back(CachedConstValueAtCodegen{const_ref, ir_sub_const}); - return ir_sub_const; + return ir_sub_const; } // just referencing `a1` in a function body // (note that `a1` occurred 2 times has 2 different ir_idx, caching above is done only within another const) @@ -1170,7 +1192,7 @@ std::vector pre_compile_symbol(SrcLocation loc, const Symbol* sym, Co std::vector ir_const = pre_compile_expr(v_force_loc, code, const_ref->declared_type); code.inside_evaluating_constant = false; code.cached_consts.clear(); - return ir_const; + return ir_const; } // referencing a global variable, copy it to a local tmp var @@ -1217,6 +1239,8 @@ static std::vector process_reference(V v, CodeBlob& co } static std::vector process_assignment(V v, CodeBlob& code, TypePtr target_type) { + insert_debug_info(v->loc, v->kind, code); + AnyExprV lhs = v->get_lhs(); AnyExprV rhs = v->get_rhs(); @@ -1268,17 +1292,37 @@ static std::vector process_binary_operator(V v, std::vector cond = pre_compile_expr(v->get_lhs(), code, nullptr); tolk_assert(cond.size() == 1); std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(ternary)"); + + insert_debug_info(v->loc, ast_binary_operator, code); + Op& if_op = code.emplace_back(v->loc, Op::_If, cond); code.push_set_cur(if_op.block0); + + if (t == tok_logical_or) { + insert_debug_info(v->loc, ast_binary_operator, code, 0, "lhs of || is true"); + } + // For &&: true-branch evaluates RHS; mark RHS location + if (t == tok_logical_and) { + insert_debug_info(v->get_rhs()->loc, ast_binary_operator, code); + } code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_b_ne_0 : v_1, code, nullptr)); code.close_pop_cur(v->loc); code.push_set_cur(if_op.block1); + + // For ||: false-branch evaluates RHS; mark RHS location + if (t == tok_logical_or) { + insert_debug_info(v->get_rhs()->loc, ast_binary_operator, code); + } + if (t == tok_logical_and) { + insert_debug_info(v->loc, ast_binary_operator, code, 0, "rhs of && is false"); + } code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_0 : v_b_ne_0, code, nullptr)); code.close_pop_cur(v->loc); return transition_to_target_type(std::move(rvect), code, target_type, v); } if (t == tok_eq || t == tok_neq) { if (v->get_lhs()->inferred_type->unwrap_alias() == TypeDataAddress::create() && v->get_rhs()->inferred_type->unwrap_alias() == TypeDataAddress::create()) { + insert_debug_info(v->loc, ast_binary_operator, code); FunctionPtr f_sliceEq = lookup_function("slice.bitsEqual"); std::vector ir_lhs_slice = pre_compile_expr(v->get_lhs(), code); std::vector ir_rhs_slice = pre_compile_expr(v->get_rhs(), code); @@ -1296,12 +1340,14 @@ static std::vector process_binary_operator(V v, } static std::vector process_unary_operator(V v, CodeBlob& code, TypePtr target_type) { + insert_debug_info(v->loc, ast_unary_operator, code); std::vector rhs_vars = pre_compile_expr(v->get_rhs(), code, nullptr); std::vector rvect = gen_op_call(code, v->inferred_type, v->loc, std::move(rhs_vars), v->fun_ref, "(unary-op)"); return transition_to_target_type(std::move(rvect), code, target_type, v); } static std::vector process_ternary_operator(V v, CodeBlob& code, TypePtr target_type) { + insert_debug_info(v->loc, ast_ternary_operator, code); std::vector cond = pre_compile_expr(v->get_cond(), code, nullptr); tolk_assert(cond.size() == 1); std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(cond)"); @@ -1329,12 +1375,14 @@ static std::vector process_ternary_operator(V v } static std::vector process_cast_as_operator(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { + insert_debug_info(v->loc, ast_cast_as_operator, code); TypePtr child_target_type = v->type_node->resolved_type; std::vector rvect = pre_compile_expr(v->get_expr(), code, child_target_type, lval_ctx); return transition_to_target_type(std::move(rvect), code, target_type, v); } static std::vector process_is_type_operator(V v, CodeBlob& code, TypePtr target_type) { + insert_debug_info(v->loc, ast_is_type_operator, code); TypePtr lhs_type = v->get_expr()->inferred_type; TypePtr cmp_type = v->type_node->resolved_type; bool is_null_check = cmp_type == TypeDataNullLiteral::create(); // `v == null`, not `v is T` @@ -1351,6 +1399,7 @@ static std::vector process_is_type_operator(V v } static std::vector process_not_null_operator(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { + insert_debug_info(v->loc, ast_not_null_operator, code); TypePtr expr_type = v->get_expr()->inferred_type; TypePtr without_null_type = calculate_type_subtract_rhs_type(expr_type, TypeDataNullLiteral::create()); TypePtr child_target_type = without_null_type != TypeDataNever::create() ? without_null_type : expr_type; @@ -1367,6 +1416,7 @@ static std::vector process_lazy_operator(V v, Code FunctionPtr called_f = v_call->fun_maybe; if (called_f->is_code_function()) { // `lazy loadStorage()` is allowed, it contains just `return ...`, inline it here + insert_debug_info(v->loc, ast_function_call, code); auto f_body = called_f->ast_root->as()->get_body()->as(); tolk_assert(f_body->size() == 1 && f_body->get_item(0)->kind == ast_return_statement); auto f_returns = f_body->get_item(0)->as(); @@ -1382,14 +1432,17 @@ static std::vector process_lazy_operator(V v, Code bool has_passed_options = false; if (f_name == "T.fromSlice") { std::vector passed_slice = pre_compile_expr(v_call->get_arg(0)->get_expr(), code); + insert_debug_info(v_call->loc, ast_function_call, code); code.emplace_back(v->loc, Op::_Let, ir_slice, std::move(passed_slice)); has_passed_options = v_call->get_num_args() == 2; } else if (f_name == "T.fromCell") { std::vector ir_cell = pre_compile_expr(v_call->get_arg(0)->get_expr(), code); + insert_debug_info(v_call->loc, ast_function_call, code); code.emplace_back(v->loc, Op::_Call, ir_slice, ir_cell, lookup_function("cell.beginParse")); has_passed_options = v_call->get_num_args() == 2; } else if (f_name == "Cell.load") { std::vector ir_cell = pre_compile_expr(v_call->get_callee()->try_as()->get_obj(), code); + insert_debug_info(v_call->loc, ast_function_call, code); code.emplace_back(v->loc, Op::_Call, ir_slice, ir_cell, lookup_function("cell.beginParse")); has_passed_options = v_call->get_num_args() == 1; } else { @@ -1412,6 +1465,8 @@ static std::vector process_lazy_operator(V v, Code } static std::vector process_match_expression(V v, CodeBlob& code, TypePtr target_type) { + insert_debug_info(v->loc, ast_match_expression, code); + TypePtr subject_type = v->get_subject()->inferred_type; const TypeDataEnum* subject_enum = subject_type->unwrap_alias()->try_as(); @@ -1462,15 +1517,18 @@ static std::vector process_match_expression(V v if (is_match_by_type) { TypePtr cmp_type = v_ith_arm->pattern_type_node->resolved_type; tolk_assert(!cmp_type->unwrap_alias()->try_as()); // `match` over `int|slice` is a type checker error + insert_debug_info(v_ith_arm->loc, ast_function_call, code); eq_ith_ir_idx = pre_compile_is_type(code, subject_type, cmp_type, subj_ir_idx, v_ith_arm->loc, "(arm-cond-eq)"); } else { std::vector ith_ir_idx = pre_compile_expr(v_ith_arm->get_pattern_expr(), code); tolk_assert(subj_ir_idx.size() == 1 && ith_ir_idx.size() == 1); eq_ith_ir_idx = code.create_tmp_var(TypeDataBool::create(), v_ith_arm->loc, "(arm-cond-eq)"); + insert_debug_info(v_ith_arm->loc, ast_function_call, code); code.emplace_back(v_ith_arm->loc, Op::_Call, eq_ith_ir_idx, std::vector{subj_ir_idx[0], ith_ir_idx[0]}, eq_sym); } Op& if_op = code.emplace_back(v_ith_arm->loc, Op::_If, std::move(eq_ith_ir_idx)); code.push_set_cur(if_op.block0); + insert_debug_info(v_ith_arm->loc, ast_match_arm, code); if (v->is_statement()) { pre_compile_expr(v_ith_arm->get_body(), code); if (v == stmt_before_immediate_return) { @@ -1488,6 +1546,7 @@ static std::vector process_match_expression(V v // we're inside the last ELSE auto v_last_arm = v->get_arm(n_arms - 1); if (v->is_statement()) { + insert_debug_info(v_last_arm->loc, ast_match_arm, code); pre_compile_expr(v_last_arm->get_body(), code); if (v == stmt_before_immediate_return) { code.emplace_back(v_last_arm->loc, Op::_Return); @@ -1624,6 +1683,7 @@ static std::vector process_function_call(V v, Code std::vector tfunc = pre_compile_expr(v->get_callee(), code, nullptr); tolk_assert(tfunc.size() == 1); args_vars.push_back(tfunc[0]); + insert_debug_info(v->loc, ast_function_call, code); std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(call-ind)"); Op& op = code.emplace_back(v->loc, Op::_CallInd, rvect, std::move(args_vars)); op.set_impure_flag(); @@ -1765,6 +1825,7 @@ static std::vector process_braced_expression(V static std::vector process_tensor(V v, CodeBlob& code, TypePtr target_type, LValContext* lval_ctx) { // tensor is compiled "as is", for example `(1, null)` occupies 2 slots // and if assigned/passed to something other, like `(int, (int,int)?)`, a whole tensor is transitioned, it works + insert_debug_info(v->loc, ast_tensor, code); std::vector rvect = pre_compile_tensor(code, v->get_items(), lval_ctx); return transition_to_target_type(std::move(rvect), code, target_type, v); } @@ -1773,6 +1834,7 @@ static std::vector process_typed_tuple(V v, CodeBl if (lval_ctx) { // todo some time, make "var (a, [b,c]) = (1, [2,3])" work v->error("[...] can not be used as lvalue here"); } + insert_debug_info(v->loc, ast_bracket_tuple, code); std::vector left = code.create_tmp_var(v->inferred_type, v->loc, "(pack-tuple)"); std::vector right = pre_compile_tensor(code, v->get_items(), lval_ctx); code.emplace_back(v->loc, Op::_Tuple, left, std::move(right)); @@ -1835,6 +1897,7 @@ static std::vector process_object_literal(V v, Co // an object (an instance of a struct) is actually a tensor at low-level // for example, `struct User { id: int; name: slice; }` occupies 2 slots // fields of a tensor are placed in order of declaration (in a literal they might be shuffled) + insert_debug_info(v->loc, ast_object_literal, code); bool are_fields_shuffled = false; for (int i = 1; i < v->get_body()->get_num_fields(); ++i) { StructFieldPtr field_ref = v->struct_ref->find_field(v->get_body()->get_field(i)->get_field_name()); @@ -1884,6 +1947,7 @@ static std::vector process_object_literal(V v, Co } static std::vector process_int_const(V v, CodeBlob& code, TypePtr target_type) { + insert_debug_info(v, code); std::vector rvect = code.create_tmp_var(v->inferred_type, v->loc, "(int-const)"); code.emplace_back(v->loc, Op::_IntConst, rvect, v->intval); // here, like everywhere, even for just `int`, there might be a potential transition due to union types @@ -1987,12 +2051,14 @@ static std::vector process_artificial_aux_vertex(Vget_arms_count(); ++i) { auto v_arm = v_match->get_arm(i); TypePtr arm_variant = nullptr; + AnyV arm_variant_node = nullptr; if (v_arm->pattern_kind == MatchArmKind::exact_type) { arm_variant = v_arm->pattern_type_node->resolved_type; + arm_variant_node = v_arm->pattern_type_node; } else { tolk_assert(v_arm->pattern_kind == MatchArmKind::else_branch); // `else` allowed in a lazy match } - match_blocks.emplace_back(LazyMatchOptions::MatchBlock{arm_variant, v_arm->get_body(), v_arm->get_body()->inferred_type}); + match_blocks.emplace_back(LazyMatchOptions::MatchBlock{arm_variant, arm_variant_node, v_arm->get_body(), v_arm->get_body()->inferred_type}); } LazyMatchOptions options = { @@ -2107,13 +2173,23 @@ static void process_block_statement(V v, CodeBlob& code) { } static void process_assert_statement(V v, CodeBlob& code) { + const auto cond = v->get_cond(); + + insert_debug_info(v->loc, ast_assert_statement, code); std::vector ir_thrown_code = pre_compile_expr(v->get_thrown_code(), code); - std::vector ir_cond = pre_compile_expr(v->get_cond(), code); + std::vector ir_cond = pre_compile_expr(cond, code); tolk_assert(ir_cond.size() == 1 && ir_thrown_code.size() == 1); std::vector args_vars = { ir_thrown_code[0], ir_cond[0], code.create_int(v->loc, 0, "(assert-0)") }; FunctionPtr builtin_sym = lookup_function("__throw_if_unless"); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym, "(throw-call)"); + + if (G.settings.collect_source_map && G.source_map.size() > 0) { + const auto cond_loc = cond->loc.get_src_file()->convert_offset(cond->loc.get_char_offset()); + auto& last_entry = G.source_map.at(G.source_map.size() - 1); + last_entry.descr = std::string(cond_loc.line_str); + last_entry.is_assert_throw = true; + } } static void process_catch_variable(AnyExprV v_catch_var, CodeBlob& code) { @@ -2125,6 +2201,7 @@ static void process_catch_variable(AnyExprV v_catch_var, CodeBlob& code) { } static void process_try_catch_statement(V v, CodeBlob& code) { + insert_debug_info(v->loc, ast_try_catch_statement, code); code.require_callxargs = true; Op& try_catch_op = code.emplace_back(v->loc, Op::_TryCatch); code.push_set_cur(try_catch_op.block0); @@ -2143,6 +2220,7 @@ static void process_try_catch_statement(V v, CodeBlob& } static void process_repeat_statement(V v, CodeBlob& code) { + insert_debug_info(v->loc, ast_repeat_statement, code); std::vector tmp_vars = pre_compile_expr(v->get_cond(), code, nullptr); Op& repeat_op = code.emplace_back(v->loc, Op::_Repeat, tmp_vars); code.push_set_cur(repeat_op.block0); @@ -2151,6 +2229,7 @@ static void process_repeat_statement(V v, CodeBlob& code) } static void process_if_statement(V v, CodeBlob& code) { + insert_debug_info(v->loc, ast_if_statement, code); std::vector cond = pre_compile_expr(v->get_cond(), code, nullptr); tolk_assert(cond.size() == 1); @@ -2182,6 +2261,7 @@ static void process_if_statement(V v, CodeBlob& code) { } static void process_do_while_statement(V v, CodeBlob& code) { + insert_debug_info(v->loc, ast_do_while_statement, code); Op& until_op = code.emplace_back(v->loc, Op::_Until); code.push_set_cur(until_op.block0); process_any_statement(v->get_body(), code); @@ -2224,6 +2304,7 @@ static void process_do_while_statement(V v, CodeBlob& co } static void process_while_statement(V v, CodeBlob& code) { + insert_debug_info(v->loc, ast_while_statement, code); Op& while_op = code.emplace_back(v->loc, Op::_While); code.push_set_cur(while_op.block0); while_op.left = pre_compile_expr(v->get_cond(), code, nullptr); @@ -2270,6 +2351,9 @@ static void process_return_statement(V v, CodeBlob& code) return_vars.insert(return_vars.begin(), mutated_vars.begin(), mutated_vars.end()); } + // Explicitly mark as leave instruction + insert_debug_info(v->loc, ast_return_statement, code, true); + // if fun_ref is called and inlined into a parent, assign a result instead of generating a return statement if (code.inline_rvect_out) { code.emplace_back(v->loc, Op::_Let, *code.inline_rvect_out, std::move(return_vars)); @@ -2323,7 +2407,8 @@ void process_any_statement(AnyV v, CodeBlob& code) { } static void convert_function_body_to_CodeBlob(FunctionPtr fun_ref, FunctionBodyCode* code_body) { - auto v_body = fun_ref->ast_root->as()->get_body()->as(); + auto v_fun_decl = fun_ref->ast_root->as(); + auto v_body = v_fun_decl->get_body()->as(); CodeBlob* blob = new CodeBlob(fun_ref); std::vector rvect_import; @@ -2343,6 +2428,8 @@ static void convert_function_body_to_CodeBlob(FunctionPtr fun_ref, FunctionBodyC blob->in_var_cnt = blob->var_cnt; tolk_assert(blob->var_cnt == total_arg_width); + insert_debug_info(v_fun_decl->get_identifier()->loc, ast_function_declaration, *blob); + if (fun_ref->name == "onInternalMessage") { handle_onInternalMessage_codegen_start(fun_ref, rvect_import, *blob, fun_ref->loc); } diff --git a/tolk/pipe-generate-source-map.cpp b/tolk/pipe-generate-source-map.cpp new file mode 100644 index 000000000..208101239 --- /dev/null +++ b/tolk/pipe-generate-source-map.cpp @@ -0,0 +1,296 @@ +#include "tolk.h" +#include "pipeline.h" +#include "compiler-state.h" +#include "tolk-version.h" +#include "type-system.h" +#include "td/utils/JsonBuilder.h" +#include + +namespace tolk { + +static std::string extract_assert_condition(const std::string& assert_str); + +void pipeline_generate_source_map(std::ostream& debug_out) { + if (!G.settings.collect_source_map) { + return; + } + + td::JsonBuilder root_builder; + auto root_builder_obj = root_builder.enter_object(); + + root_builder_obj("version", "1.0.0"); + root_builder_obj("language", "tolk"); + root_builder_obj("compiler_version", TOLK_VERSION); + + { + td::JsonBuilder jsonb; + auto array_builder = jsonb.enter_array(); + for (const auto& file : G.all_src_files) { + auto value_builder = array_builder.enter_value(); + auto ob = value_builder.enter_object(); + + ob("path", file->realpath); + ob("is_stdlib", td::JsonBool(file->is_stdlib_file)); + ob("content", file->text); + } + array_builder.leave(); + + root_builder_obj("files", td::JsonRaw(jsonb.string_builder().as_cslice())); + } + + { + td::JsonBuilder jsonb; + auto array_builder = jsonb.enter_array(); + for (const auto& glob_var : G.all_global_vars) { + auto value_builder = array_builder.enter_value(); + auto ob = value_builder.enter_object(); + + ob("name", glob_var->name); + ob("type", glob_var->declared_type->as_human_readable()); + + const auto global_loc = glob_var->loc; + if (const SrcFile* src_file = global_loc.get_src_file(); src_file != nullptr) { + const auto pos = src_file->convert_offset(global_loc.get_char_offset()); + + td::JsonBuilder locb; + auto loc_builder = locb.enter_object(); + loc_builder("file", src_file->realpath); + loc_builder("line", static_cast(pos.line_no - 1)); + loc_builder("column", static_cast(pos.char_no - 1)); + loc_builder("end_line", static_cast(0)); + loc_builder("end_column", static_cast(0)); + loc_builder("length", static_cast(1)); + loc_builder.leave(); + + ob("loc", td::JsonRaw(locb.string_builder().as_cslice())); + } + } + array_builder.leave(); + + root_builder_obj("globals", td::JsonRaw(jsonb.string_builder().as_cslice())); + } + + { + td::JsonBuilder jsonb; + auto array_builder = jsonb.enter_array(); + + for (size_t i = 0; i < G.source_map.size(); ++i) { + const auto& entry = G.source_map[i]; + auto value_builder = array_builder.enter_value(); + auto ob = value_builder.enter_object(); + + ob("idx", td::JsonRaw(std::to_string(entry.idx))); + +#ifdef TOLK_DEBUG + { + td::JsonBuilder debugb; + auto debug_builder = debugb.enter_object(); + if (i + 1 < G.source_map.size()) { + debug_builder("opcode", G.source_map[i + 1].opcode); + } + + if (const auto file = G.all_src_files.find_file(entry.loc.file)) { + const auto& pos = file->convert_offset(entry.loc.offset); + const auto line = std::string(pos.line_str); + debug_builder("line_str", line); + + std::string underline = ""; + for (int j = 0; j < entry.loc.col; ++j) { + underline += " "; + } + underline += "^"; + + debug_builder("line_off", underline); + } + debug_builder.leave(); + + ob("debug", td::JsonRaw(debugb.string_builder().as_cslice())); + } +#endif + + { + td::JsonBuilder locb; + auto loc_builder = locb.enter_object(); + loc_builder("file", entry.loc.file); + loc_builder("line", static_cast(entry.loc.line - 1)); + loc_builder("column", static_cast(entry.loc.col - 1)); + loc_builder("end_line", static_cast(0)); + loc_builder("end_column", static_cast(0)); + loc_builder("length", static_cast(entry.loc.length)); + loc_builder.leave(); + + ob("loc", td::JsonRaw(locb.string_builder().as_cslice())); + } + + td::JsonBuilder var_builder; + auto var_array_builder = var_builder.enter_array(); + for (const auto& [var, value] : entry.vars) { + auto var_array_builder_value = var_array_builder.enter_value(); + auto var_array_value_object = var_array_builder_value.enter_object(); + + var_array_value_object("name", var.name.empty() ? "'" + std::to_string(var.ir_idx) : var.name); + var_array_value_object("type", var.v_type == nullptr ? "" : var.v_type->as_human_readable()); + + if (!var.name.empty() && (var.name[0] == '\'' || var.name == "lazyS")) { + // '1 or lazyS + var_array_value_object("is_temporary", td::JsonBool(true)); + } + + if (var.parent_type != nullptr) { + const auto union_parent = var.parent_type->try_as(); + if (union_parent != nullptr) { + td::JsonBuilder parent_type_builder; + auto parent_type_array_builder = parent_type_builder.enter_array(); + + for (auto variant : union_parent->variants) { + auto array_value = parent_type_array_builder.enter_value(); + array_value << variant->as_human_readable(); + } + + parent_type_array_builder.leave(); + var_array_value_object("possible_qualifier_types", + td::JsonRaw(parent_type_builder.string_builder().as_cslice())); + } + } + + if (!value.empty()) { + var_array_value_object("constant_value", value); + } + } + var_array_builder.leave(); + + ob("variables", td::JsonRaw(var_builder.string_builder().as_cslice())); + + { + td::JsonBuilder ctxb; + auto ctx_builder = ctxb.enter_object(); + + { + td::JsonBuilder descb; + auto desc_builder = descb.enter_object(); + desc_builder("ast_kind", entry.ast_kind); + + if (entry.ast_kind == "ast_function_call" && entry.is_assert_throw && !entry.descr.empty()) { + std::string condition = extract_assert_condition(entry.descr); + desc_builder("condition", condition); + desc_builder("is_assert_throw", td::JsonBool(true)); + } else if (entry.ast_kind == "ast_binary_operator" && !entry.descr.empty()) { + desc_builder("description", entry.descr); + } + + desc_builder.leave(); + ctx_builder("description", td::JsonRaw(descb.string_builder().as_cslice())); + } + + { + td::JsonBuilder inlb; + auto inl_builder = inlb.enter_object(); + if (entry.inlined_to_func_name != "") { + inl_builder("inlined_to_func", entry.inlined_to_func_name); + } + inl_builder("containing_func_inline_mode", static_cast(entry.func_inline_mode)); + inl_builder.leave(); + ctx_builder("inlining", td::JsonRaw(inlb.string_builder().as_cslice())); + } + + std::string event_type; + if (entry.is_entry) { + event_type = "EnterFunction"; + } else if (entry.is_leave) { + event_type = "LeaveFunction"; + } else if (entry.before_inlined_function_call) { + event_type = "EnterInlinedFunction"; + } else if (entry.after_inlined_function_call) { + event_type = "LeaveInlinedFunction"; + } + + if (event_type != "") { + ctx_builder("event", event_type); + } + + ctx_builder("containing_function", entry.func_name); + + ctx_builder.leave(); + + ob("context", td::JsonRaw(ctxb.string_builder().as_cslice())); + } + } + array_builder.leave(); + + root_builder_obj("locations", td::JsonRaw(jsonb.string_builder().as_cslice())); + } + + root_builder_obj.leave(); + + debug_out << root_builder.string_builder().as_cslice().str(); +} + +/// Extracts condition from assert statement string +/// Examples: +/// "assert (a > 10) throw 5" -> "a > 10" +/// "assert(a > 10, 5)" -> "a > 10" +static std::string extract_assert_condition(const std::string& assert_str) { + if (assert_str.empty()) { + return ""; + } + + std::string s = assert_str; + + size_t start = s.find_first_not_of(" \t"); + if (start != std::string::npos) { + s = s.substr(start); + } else { + return ""; + } + + if (s.substr(0, 6) == "assert") { + s = s.substr(6); + } + + start = s.find_first_not_of(" \t"); + if (start != std::string::npos) { + s = s.substr(start); + } else { + return ""; + } + + if (!s.empty() && s[0] == '(') { + // Format: assert (condition) throw/error + if (size_t paren_end = s.rfind(')'); paren_end != std::string::npos) { + std::string condition = s.substr(1, paren_end - 1); + const size_t cond_start = condition.find_first_not_of(" \t"); + size_t cond_end = condition.find_last_not_of(" \t"); + if (cond_start != std::string::npos && cond_end != std::string::npos) { + return condition.substr(cond_start, cond_end - cond_start + 1); + } + return condition; + } + } else if (!s.empty()) { + // Format: assert(condition, error) or assert condition throw/error + size_t comma_pos = s.rfind(','); + size_t throw_pos = s.find(" throw"); + size_t error_pos = s.find(" error"); + + size_t end_pos = std::string::npos; + if (comma_pos != std::string::npos) { + end_pos = comma_pos; + } else if (throw_pos != std::string::npos) { + end_pos = throw_pos; + } else if (error_pos != std::string::npos) { + end_pos = error_pos; + } + + if (end_pos != std::string::npos) { + std::string condition = s.substr(0, end_pos); + size_t cond_end = condition.find_last_not_of(" \t"); + if (cond_end != std::string::npos) { + return condition.substr(0, cond_end + 1); + } + return condition; + } + } + + return s; +} + +} // namespace tolk diff --git a/tolk/pipeline.h b/tolk/pipeline.h index d433afcfc..6cb45b411 100644 --- a/tolk/pipeline.h +++ b/tolk/pipeline.h @@ -52,6 +52,7 @@ void pipeline_convert_ast_to_legacy_Expr_Op(); void pipeline_find_unused_symbols(); void pipeline_generate_fif_output_to_std_cout(); +void pipeline_generate_source_map(std::ostream& debug_out); // these pipes also can be called per-function individually // they are called for instantiated generics functions, when `f` is deeply cloned as `f` diff --git a/tolk/send-message-api.cpp b/tolk/send-message-api.cpp index b5ef61aa7..b0dcda209 100644 --- a/tolk/send-message-api.cpp +++ b/tolk/send-message-api.cpp @@ -87,6 +87,8 @@ struct IR_AutoDeployAddress { // fun createMessage(options: CreateMessageOptions): OutMessage std::vector generate_createMessage(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& ir_options) { + insert_debug_info(loc, ast_function_call, code); + TypePtr bodyT = called_f->substitutedTs->typeT_at(0); StructPtr s_Options = lookup_global_symbol("CreateMessageOptions")->try_as(); StructPtr s_AutoDeployAddress = lookup_global_symbol("AutoDeployAddress")->try_as(); @@ -357,6 +359,8 @@ std::vector generate_createMessage(FunctionPtr called_f, CodeBlob& co // fun createExternalLogMessage(options: CreateExternalLogMessageOptions): OutMessage std::vector generate_createExternalLogMessage(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& args) { + insert_debug_info(loc, ast_function_call, code); + TypePtr bodyT = called_f->substitutedTs->typeT_at(0); StructPtr s_Options = lookup_global_symbol("CreateExternalLogMessageOptions")->try_as(); StructPtr s_ExtOutLogBucket = lookup_global_symbol("ExtOutLogBucket")->try_as(); @@ -494,6 +498,8 @@ std::vector generate_createExternalLogMessage(FunctionPtr called_f, C // fun address.buildSameAddressInAnotherShard(self, options: AddressShardingOptions): builder std::vector generate_address_buildInAnotherShard(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& args) { + insert_debug_info(loc, ast_function_call, code); + std::vector ir_shard_options = args[1]; tolk_assert(ir_shard_options.size() == 2); @@ -527,6 +533,7 @@ std::vector generate_address_buildInAnotherShard(FunctionPtr called_f // fun AutoDeployAddress.buildAddress(self): builder std::vector generate_AutoDeployAddress_buildAddress(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& ir_options) { + insert_debug_info(loc, ast_function_call, code); IR_AutoDeployAddress ir_self(code, loc, ir_options[0]); std::vector ir_builder = code.create_tmp_var(TypeDataSlice::create(), loc, "(addr-b)"); @@ -606,6 +613,7 @@ std::vector generate_AutoDeployAddress_buildAddress(FunctionPtr calle // fun AutoDeployAddress.addressMatches(self, addr: address): bool std::vector generate_AutoDeployAddress_addressMatches(FunctionPtr called_f, CodeBlob& code, SrcLocation loc, const std::vector>& ir_self_and_addr) { + insert_debug_info(loc, ast_function_call, code); IR_AutoDeployAddress ir_self(code, loc, ir_self_and_addr[0]); // at first, calculate stateInitHash = (hash of StateInit cell would be, but without constructing a cell) diff --git a/tolk/source-map-schema-v1.json b/tolk/source-map-schema-v1.json new file mode 100644 index 000000000..1863420f4 --- /dev/null +++ b/tolk/source-map-schema-v1.json @@ -0,0 +1,320 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Tolk Source Map Schema v1", + "description": "JSON Schema for Tolk compiler source map v1 files", + "type": "object", + "properties": { + "version": { + "description": "Schema version", + "type": "string", + "enum": [ + "1.0.0" + ] + }, + "language": { + "description": "The language for which this source map was generated", + "type": "string", + "enum": [ + "tolk", + "func" + ] + }, + "compiler_version": { + "description": "The compiler version that generated this source map", + "type": "string" + }, + "files": { + "description": "Array of source files processed by the compiler", + "type": "array", + "items": { + "$ref": "#/$defs/sourceFile" + } + }, + "globals": { + "description": "Array of global variables declared in the program", + "type": "array", + "items": { + "$ref": "#/$defs/globalVariable" + } + }, + "locations": { + "description": "Array of debug locations with variable state information", + "type": "array", + "items": { + "$ref": "#/$defs/debugLocation" + } + } + }, + "required": [ + "version", + "files", + "globals", + "locations" + ], + "$defs": { + "sourceFile": { + "description": "Information about a source file", + "type": "object", + "properties": { + "path": { + "description": "Path to the source file", + "type": "string" + }, + "is_stdlib": { + "description": "Whether this is a standard library file", + "type": "boolean" + }, + "content": { + "description": "Complete source code content of the file", + "type": "string" + } + }, + "required": [ + "path", + "is_stdlib", + "content" + ] + }, + "globalVariable": { + "description": "Information about a global variable", + "type": "object", + "properties": { + "name": { + "description": "Name of the global variable", + "type": "string" + }, + "type": "string", + "loc": { + "description": "Location of this global variable declaration", + "$ref": "#/$defs/sourceLocation" + } + }, + "required": [ + "name", + "type", + "loc" + ] + }, + "sourceLocation": { + "description": "Source code location information", + "type": "object", + "properties": { + "file": { + "description": "Path to the source file", + "type": "string" + }, + "line": { + "description": "0-based line number", + "type": "integer" + }, + "column": { + "description": "0-based column number", + "type": "integer" + }, + "end_line": { + "description": "0-based end line number (currently always 0)", + "type": "integer" + }, + "end_column": { + "description": "0-based end column number (currently always 0)", + "type": "integer" + }, + "length": { + "description": "Length of the relevant code segment in characters", + "type": "integer" + } + }, + "required": [ + "file", + "line", + "column", + "end_line", + "end_column", + "length" + ] + }, + "entryContextDescription": { + "description": "Description of the entry context depending on AST kind", + "oneOf": [ + { + "description": "Basic entry with AST kind and other optional properties", + "type": "object", + "properties": { + "ast_kind": { + "description": "AST node type", + "type": "string" + } + }, + "required": ["ast_kind"] + }, + { + "description": "Assert statement entry", + "type": "object", + "properties": { + "ast_kind": { + "description": "AST node type", + "type": "string", + "enum": ["ast_function_call"] + }, + "is_assert_throw": { + "description": "Whether this is an assert throw call", + "type": "boolean", + "enum": [true] + }, + "condition": { + "description": "Extracted assert condition", + "type": "string" + } + }, + "required": ["ast_kind", "is_assert_throw", "condition"] + }, + { + "description": "Binary operator entry", + "type": "object", + "properties": { + "ast_kind": { + "description": "AST node type", + "type": "string", + "enum": ["ast_binary_operator"] + }, + "description": { + "description": "Human-readable description for complex expressions", + "type": "string" + } + }, + "required": ["ast_kind", "description"] + } + ] + }, + "inliningInfo": { + "description": "Inlining information for the entry", + "type": "object", + "properties": { + "inlined_to_func": { + "description": "Name of function where this was inlined (if applicable)", + "type": "string" + }, + "containing_func_inline_mode": { + "description": "Inlining mode of the containing function", + "type": "integer", + "enum": [0, 1, 2, 3, 4] + } + }, + "required": ["containing_func_inline_mode"] + }, + "entryContext": { + "description": "Execution context for a debug location", + "type": "object", + "properties": { + "description": "Context description depending on AST kind", + "inlining": { + "description": "Inlining properties of current entry", + "$ref": "#/$defs/inliningInfo" + }, + "event": { + "description": "Pseudo-event type", + "type": "string", + "enum": [ + "EnterFunction", + "LeaveFunction", + "EnterInlinedFunction", + "ExitInlinedFunction" + ] + }, + "containing_function": { + "description": "Name of the containing function", + "type": "string" + } + }, + "required": [ + "description", + "inlining", + "containing_function" + ] + }, + "debugInfo": { + "description": "Debug-specific information (only present in debug builds)", + "type": "object", + "properties": { + "opcode": { + "description": "Generated TVM opcode", + "type": "string" + }, + "line_str": { + "description": "Source line content", + "type": "string" + }, + "line_off": { + "description": "Visual pointer showing the location in the source line (spaces followed by ^)", + "type": "string" + } + } + }, + "debugLocation": { + "description": "Debug location with variable state and context", + "type": "object", + "properties": { + "idx": { + "description": "Unique identifier for this debug location", + "type": "integer", + "minimum": 0 + }, + "loc": { + "description": "Source code location", + "$ref": "#/$defs/sourceLocation" + }, + "variables": { + "description": "Variables available at this debug location", + "type": "array", + "items": { + "$ref": "#/$defs/variable" + } + }, + "context": { + "description": "Execution context information", + "$ref": "#/$defs/entryContext" + }, + "debug": { + "description": "Debug-specific information", + "$ref": "#/$defs/debugInfo" + } + }, + "required": [ + "idx", + "loc", + "variables", + "context" + ] + }, + "variable": { + "description": "Variable information at a debug location", + "type": "object", + "properties": { + "name": { + "description": "Variable name (may start with ' for temporary variables)", + "type": "string" + }, + "type": "string", + "is_temporary": { + "description": "Whether this is a temporary variable", + "type": "boolean" + }, + "constant_value": { + "description": "Constant value if variable has one", + "type": "string" + }, + "possible_qualifier_types": { + "description": "Possible types for union variables", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "name", + "type" + ] + } + } +} diff --git a/tolk/src-file.cpp b/tolk/src-file.cpp index 07db0eb88..a5b42570d 100644 --- a/tolk/src-file.cpp +++ b/tolk/src-file.cpp @@ -145,6 +145,9 @@ std::ostream& operator<<(std::ostream& os, const Fatal& fatal) { } const SrcFile* SrcLocation::get_src_file() const { + if (file_id == -1) { + return nullptr; + } return G.all_src_files.get_file(file_id); } diff --git a/tolk/src-file.h b/tolk/src-file.h index 9b39a0acc..a42baffed 100644 --- a/tolk/src-file.h +++ b/tolk/src-file.h @@ -76,6 +76,7 @@ class SrcLocation { bool is_defined() const { return file_id != -1; } bool is_stdlib() const { return file_id == 0; } + int get_char_offset() const { return char_offset; }; const SrcFile* get_src_file() const; // similar to `this->get_src_file() == symbol->get_src_file() || symbol->get_src_file()->is_stdlib()` diff --git a/tolk/tolk-main.cpp b/tolk/tolk-main.cpp index 17a2b4c84..cacd68fa5 100644 --- a/tolk/tolk-main.cpp +++ b/tolk/tolk-main.cpp @@ -51,6 +51,7 @@ void usage(const char* progname) { "-x\tEnables experimental options, comma-separated\n" "-S\tDon't include stack layout comments into Fift output\n" "-L\tDon't include original lines from Tolk src into Fift output\n" + "-d\tCollect source map\n" "-e\tIncreases verbosity level (extra output into stderr)\n" "-v\tOutput version of Tolk and exit\n"; std::exit(2); @@ -211,7 +212,7 @@ class StdCoutRedirectToFile { int main(int argc, char* const argv[]) { int i; - while ((i = getopt(argc, argv, "o:b:O:x:SLevh")) != -1) { + while ((i = getopt(argc, argv, "o:b:O:x:d:SLevh")) != -1) { switch (i) { case 'o': G.settings.output_filename = optarg; @@ -234,6 +235,10 @@ int main(int argc, char* const argv[]) { case 'e': G.settings.verbosity++; break; + case 'd': + G.settings.collect_source_map = true; + G.settings.source_map_output_filename = optarg; + break; case 'v': std::cout << "Tolk compiler v" << TOLK_VERSION << std::endl; std::cout << "Build commit: " << GitMetadata::CommitSHA1() << std::endl; @@ -280,6 +285,18 @@ int main(int argc, char* const argv[]) { G.settings.read_callback = fs_read_callback; - int exit_code = tolk_proceed(argv[optind]); + const auto source_map_filename = + G.settings.source_map_output_filename.empty() ? "./source_map.json" : G.settings.source_map_output_filename; + + std::ofstream source_map_out; + if (G.settings.collect_source_map) { + source_map_out.open(source_map_filename); + if (!source_map_out.is_open()) { + std::cerr << "Failed to create source map file " << source_map_filename << std::endl; + return 2; + } + } + + int exit_code = tolk_proceed(argv[optind], source_map_out); return exit_code; } diff --git a/tolk/tolk-wasm.cpp b/tolk/tolk-wasm.cpp index b5149481b..1f93f9e6a 100644 --- a/tolk/tolk-wasm.cpp +++ b/tolk/tolk-wasm.cpp @@ -30,10 +30,13 @@ #include "td/utils/JsonBuilder.h" #include "fift/utils.h" #include "td/utils/Status.h" +#include "td/utils/misc.h" #include using namespace tolk; +static std::string postprocess_fift_output_after_source_msp(std::string fift_code); + static td::Result compile_internal(char *config_json) { TRY_RESULT(input_json, td::json_decode(td::MutableSlice(config_json))) td::JsonObject& config = input_json.get_object(); @@ -41,6 +44,7 @@ static td::Result compile_internal(char *config_json) { TRY_RESULT(opt_level, td::get_json_object_int_field(config, "optimizationLevel", true, 2)); TRY_RESULT(stack_comments, td::get_json_object_bool_field(config, "withStackComments", true, false)); TRY_RESULT(src_line_comments, td::get_json_object_bool_field(config, "withSrcLineComments", true, false)); + TRY_RESULT(collect_source_map, td::get_json_object_bool_field(config, "collectSourceMap", true, false)); TRY_RESULT(entrypoint_filename, td::get_json_object_string_field(config, "entrypointFileName", false)); TRY_RESULT(experimental_options, td::get_json_object_string_field(config, "experimentalOptions", true)); @@ -48,19 +52,34 @@ static td::Result compile_internal(char *config_json) { G.settings.optimization_level = std::max(0, opt_level); G.settings.stack_layout_comments = stack_comments; G.settings.tolk_src_as_line_comments = src_line_comments; + G.settings.collect_source_map = collect_source_map; if (!experimental_options.empty()) { G.settings.parse_experimental_options_cmd_arg(experimental_options.c_str()); } - std::ostringstream outs, errs; + std::ostringstream outs, errs, source_map_out; std::cout.rdbuf(outs.rdbuf()); std::cerr.rdbuf(errs.rdbuf()); - int exit_code = tolk_proceed(entrypoint_filename); + int exit_code = tolk_proceed(entrypoint_filename, source_map_out); if (exit_code != 0) { return td::Status::Error(errs.str()); } - TRY_RESULT(fift_res, fift::compile_asm_program(outs.str(), "/fiftlib/")); + std::string raw_fift_code = outs.str(); + + // Due to the implementation specifics of source maps, with `collect_source_map` enabled, + // the Fift code contains additional DEBUGMARK instructions, which allow the real code in Tolk + // to be matched with specific TVM instructions after Fift compilation. + // + // Since DEBUGMARK instructions are absent from the TVM, attempting to compile and run such code + // will result in an error. Therefore, `fift_code` contains the compiled code as if + // no DEBUGMARK instructions exist. + // + // This code will be the same as the code that would be generated without source maps enabled, + // ensuring that users get the correct code in this mode. + std::string fift_code = postprocess_fift_output_after_source_msp(raw_fift_code); + + TRY_RESULT(fift_res, fift::compile_asm_program(std::move(fift_code), "/fiftlib/")); td::JsonBuilder result_json; auto obj = result_json.enter_object(); @@ -68,6 +87,22 @@ static td::Result compile_internal(char *config_json) { obj("fiftCode", fift_res.fiftCode); obj("codeBoc64", fift_res.codeBoc64); obj("codeHashHex", fift_res.codeHashHex); + + if (const auto source_map = source_map_out.str(); !source_map.empty()) { + // To correctly map Tolk code to TVM instructions, we also need to return the compiled code + // with the DEBUGMARK instructions. This "poisoned for execution" code is used for mapping in tolk-js. + // The bitcode with DEBUGMARK is recompiled into bitcode without it (i.e., valid for execution), + // and in the process, the TASM assembler assembles the mapping of the DEBUGMARK index to TVM instructions, + // which is a key part of source map construction. + auto fift_source_map_res = fift::compile_asm_program(std::move(raw_fift_code), "/fiftlib/"); + if (fift_source_map_res.is_ok()) { + const auto fift_source_map_res_ok = fift_source_map_res.move_as_ok(); + obj("fiftSourceMapCode", fift_source_map_res_ok.fiftCode); + obj("sourceMapCodeBoc64", fift_source_map_res_ok.codeBoc64); + } + obj("sourceMap", td::JsonRaw(source_map)); + } + obj("stderr", errs.str().c_str()); obj.leave(); @@ -126,4 +161,26 @@ const char *tolk_compile(char *config_json, WasmFsReadCallback callback) { return strdup(res_string.c_str()); } +static std::string postprocess_fift_output_after_source_msp(std::string fift_code) { + if (!G.settings.collect_source_map) { + // Without enabled source maps code is always good + return fift_code; + } + + std::string processed_code; + bool first = true; + for (auto& line : td::full_split(fift_code, '\n')) { + if (line.find("DEBUGMARK") != std::string::npos) { + // filter out all DEBUGMARK instructions + continue; + } + if (!first) { + processed_code.push_back('\n'); + } + first = false; + processed_code += line; + } + return processed_code; +} + } // extern "C" diff --git a/tolk/tolk.cpp b/tolk/tolk.cpp index cef2365ec..e4590de97 100644 --- a/tolk/tolk.cpp +++ b/tolk/tolk.cpp @@ -29,6 +29,7 @@ #include "lexer.h" #include "ast.h" #include "type-system.h" +#include namespace tolk { @@ -45,7 +46,7 @@ void on_assertion_failed(const char *description, const char *file_name, int lin throw Fatal(std::move(message)); } -int tolk_proceed(const std::string &entrypoint_filename) { +int tolk_proceed(const std::string &entrypoint_filename, std::ostream& source_map_out) { type_system_init(); define_builtins(); lexer_init(); @@ -75,6 +76,7 @@ int tolk_proceed(const std::string &entrypoint_filename) { pipeline_find_unused_symbols(); pipeline_generate_fif_output_to_std_cout(); + pipeline_generate_source_map(source_map_out); return 0; } catch (Fatal& fatal) { diff --git a/tolk/tolk.h b/tolk/tolk.h index 13a9acefe..15689a118 100644 --- a/tolk/tolk.h +++ b/tolk/tolk.h @@ -21,6 +21,8 @@ #include "symtable.h" #include "crypto/common/refint.h" #include "td/utils/Status.h" + +#include #include #include #include @@ -53,15 +55,17 @@ struct TmpVar { TypePtr v_type; // get_width_on_stack() is 1 std::string name; // "x" for vars originated from user sources; "x.0" for tensor components; empty for implicitly created tmp vars SrcLocation loc; // location of var declaration in sources or where a tmp var was originated + TypePtr parent_type = nullptr; // type of "stack" in "stack.USlot1" #ifdef TOLK_DEBUG const char* desc = nullptr; // "origin" of tmp var, for debug output like `'15 (binary-op) '16 (glob-var)` #endif - TmpVar(var_idx_t ir_idx, TypePtr v_type, std::string name, SrcLocation loc) + TmpVar(var_idx_t ir_idx, TypePtr v_type, std::string name, SrcLocation loc, TypePtr parent_type = nullptr) : ir_idx(ir_idx) , v_type(v_type) , name(std::move(name)) - , loc(loc) { + , loc(loc) + , parent_type(parent_type) { } void show_as_stack_comment(std::ostream& os) const; @@ -264,6 +268,112 @@ class ListIterator { struct Stack; +struct SourceMapLocation { + std::string file; + int offset{}; + long line{}; + long line_offset{}; + long col{}; + long length{}; +}; + +struct SourceMapVariable { + /** + * All information about variable. + */ + TmpVar data; + + /** + * If a variable has a constant value (rarely) it will be placed here. + */ + std::string constant_value; +}; + +struct SourceMapEntry { + /** + * Unique ID of this entry. + */ + size_t idx{}; + + /** + * If true, entry represents code before first statement. + */ + bool is_entry{}; + /** + * If true, entry represents code after last statement. + */ + bool is_leave{}; + + /** + * If true, entry represent assert throw call. + */ + bool is_assert_throw{}; + + /** + * Human-readable description of current entry. + */ + std::string descr{}; + + /** + * Location of this entry. + */ + SourceMapLocation loc{}; + + /** + * Variables available in current position. + */ + std::vector vars; + + /** + * Name oj outer function which contains this code. + */ + std::string func_name; + + /** + * If a function was inlined, this field will contain the name + * of the function where the code was inlined. + */ + std::string inlined_to_func_name; + + /** + * Whenever outer function is inlined and how. + */ + FunctionInlineMode func_inline_mode; + + /** + * Marks the first instruction of inlined function. + */ + bool before_inlined_function_call{false}; + + /** + * Marks the last instruction of inlined function. + */ + bool after_inlined_function_call{false}; + + /** + * The AST node for which this entry was generated. + */ + std::string ast_kind; + +#ifdef TOLK_DEBUG + /** + * String representation of `Op` for which this entry was generated. + */ + std::string opcode; +#endif +}; + +struct SourceMapGlobalVariable { + /** + * Name of this global variable. + */ + std::string name; + /** + * Human-readable type pf this global variable. + */ + std::string type; +}; + struct Op { enum OpKind { _Nop, @@ -284,6 +394,7 @@ struct Op { _Again, _TryCatch, _SliceConst, + _DebugInfo, }; OpKind cl; enum { _Disabled = 1, _NoReturn = 2, _Impure = 4, _ArgOrderAlreadyEqualsAsm = 8 }; @@ -298,6 +409,12 @@ struct Op { std::unique_ptr block0, block1; td::RefInt256 int_const; std::string str_const; + + /** + * Current ID of source map entry, see insert_debug_info. + */ + size_t source_map_entry_idx{0}; + Op(SrcLocation loc, OpKind cl) : cl(cl), flags(0), loc(loc) { } Op(SrcLocation loc, OpKind cl, const std::vector& left) @@ -597,6 +714,7 @@ struct AsmOpList { } const_idx_t register_const(td::RefInt256 new_const); td::RefInt256 get_const(const_idx_t idx); + std::optional> get_var(const std::pair& idx_pair) const; void show_var_ext(std::ostream& os, std::pair idx_pair) const; void adjust_last() { if (list_.back().is_nop()) { @@ -1031,7 +1149,7 @@ struct Stack { struct FunctionBodyBuiltinAsmOp { using CompileToAsmOpImpl = AsmOp(std::vector&, std::vector&, SrcLocation); - + std::function simple_compile; explicit FunctionBodyBuiltinAsmOp(std::function compile) @@ -1044,7 +1162,7 @@ struct FunctionBodyBuiltinGenerateOps { using GenerateOpsImpl = std::vector(FunctionPtr, CodeBlob&, SrcLocation, const std::vector>&); std::function generate_ops; - + explicit FunctionBodyBuiltinGenerateOps(std::function generate_ops) : generate_ops(std::move(generate_ops)) {} }; @@ -1069,6 +1187,9 @@ struct LazyVarRefAtCodegen { : var_ref(var_ref), var_state(var_state) {} }; +void insert_debug_info(SrcLocation loc, ASTNodeKind kind, CodeBlob& code, bool is_leave = false, std::string descr = ""); +void insert_debug_info(AnyV v, CodeBlob& code); + // CachedConstValueAtCodegen is used for a map [some_const => '5] struct CachedConstValueAtCodegen { GlobalConstPtr const_ref; @@ -1108,7 +1229,7 @@ struct CodeBlob { #endif return res; } - std::vector create_var(TypePtr var_type, SrcLocation loc, std::string name); + std::vector create_var(TypePtr var_type, SrcLocation loc, std::string name, TypePtr parent_type = nullptr); std::vector create_tmp_var(TypePtr var_type, SrcLocation loc, const char* desc) { std::vector ir_idx = create_var(var_type, loc, {}); #ifdef TOLK_DEBUG @@ -1163,7 +1284,7 @@ void patch_builtins_after_stdlib_loaded(); * */ -int tolk_proceed(const std::string &entrypoint_filename); +int tolk_proceed(const std::string &entrypoint_filename, std::ostream& source_map_out); } // namespace tolk