diff --git a/src/compiler/crystal/interpreter/compiled_block.cr b/src/compiler/crystal/interpreter/compiled_block.cr index 0b24de0a6847..f7cf1dbc0a5e 100644 --- a/src/compiler/crystal/interpreter/compiled_block.cr +++ b/src/compiler/crystal/interpreter/compiled_block.cr @@ -9,9 +9,6 @@ class Crystal::Repl # The bytecode to execute the block. getter instructions : CompiledInstructions - # Local variables for the block (they might reference variables outside of the block) - getter local_vars : LocalVars - # How many bytes occupy the block args getter args_bytesize : Int32 @@ -21,8 +18,10 @@ class Crystal::Repl # What's the byte offset where the local vars of this block end getter locals_bytesize_end : Int32 + # Local variables for the block (they might reference variables outside of the block) + property! local_vars : LocalVars + def initialize(@block : Block, - @local_vars : LocalVars, @args_bytesize : Int32, @locals_bytesize_start : Int32, @locals_bytesize_end : Int32) diff --git a/src/compiler/crystal/interpreter/compiler.cr b/src/compiler/crystal/interpreter/compiler.cr index 2edb974d8f8d..0d2906723f77 100644 --- a/src/compiler/crystal/interpreter/compiler.cr +++ b/src/compiler/crystal/interpreter/compiler.cr @@ -2030,7 +2030,7 @@ class Crystal::Repl::Compiler < Crystal::Visitor block_args_bytesize = block.args.sum { |arg| aligned_sizeof_type(arg) } - compiled_block = CompiledBlock.new(block, @local_vars, + compiled_block = CompiledBlock.new(block, args_bytesize: block_args_bytesize, locals_bytesize_start: bytesize_before_block_local_vars, locals_bytesize_end: bytesize_after_block_local_vars, @@ -2047,6 +2047,10 @@ class Crystal::Repl::Compiler < Crystal::Visitor compiler.compile_block(block, target_def, @closure_context) + # Keep a copy of the local vars before exiting the block. + # Otherwise we'll lose reference to the block's vars (useful for pry) + compiled_block.local_vars = @local_vars.dup + {% if Debug::DECOMPILE %} puts "=== #{target_def.owner}##{target_def.name}#block ===" puts Disassembler.disassemble(@context, compiled_block.instructions, @local_vars) diff --git a/src/compiler/crystal/interpreter/disassembler.cr b/src/compiler/crystal/interpreter/disassembler.cr index a73c442437a2..a7ccbcd46131 100644 --- a/src/compiler/crystal/interpreter/disassembler.cr +++ b/src/compiler/crystal/interpreter/disassembler.cr @@ -7,6 +7,10 @@ module Crystal::Repl::Disassembler disassemble(context, compiled_def.instructions, compiled_def.local_vars) end + def self.disassemble(context : Context, compiled_block : CompiledBlock) : String + disassemble(context, compiled_block.instructions, compiled_block.local_vars) + end + def self.disassemble(context : Context, instructions : CompiledInstructions, local_vars : LocalVars) : String String.build do |io| exception_handlers = instructions.exception_handlers diff --git a/src/compiler/crystal/interpreter/interpreter.cr b/src/compiler/crystal/interpreter/interpreter.cr index f58f7fab9151..011a345d6cde 100644 --- a/src/compiler/crystal/interpreter/interpreter.cr +++ b/src/compiler/crystal/interpreter/interpreter.cr @@ -7,6 +7,8 @@ class Crystal::Repl::Interpreter record CallFrame, # The CompiledDef related to this call frame compiled_def : CompiledDef, + # The CompiledBlock related to this call frame, if any + compiled_block : CompiledBlock?, # Instructions for this frame instructions : CompiledInstructions, # The pointer to the current instruction for this call frame. @@ -122,9 +124,13 @@ class Crystal::Repl::Interpreter @compiled_def = nil end - def initialize(interpreter : Interpreter, compiled_def : CompiledDef, stack : Pointer(UInt8), @block_level : Int32) + def self.new(interpreter : Interpreter, compiled_def : CompiledDef, stack : Pointer(UInt8), block_level : Int32) + new(interpreter, compiled_def, compiled_def.local_vars, stack, block_level) + end + + def initialize(interpreter : Interpreter, compiled_def : CompiledDef, local_vars : LocalVars, stack : Pointer(UInt8), @block_level : Int32) @context = interpreter.context - @local_vars = compiled_def.local_vars.dup + @local_vars = local_vars.dup @argv = interpreter.@argv @instructions = CompiledInstructions.new @@ -281,6 +287,7 @@ class Crystal::Repl::Interpreter instructions: instructions, local_vars: @local_vars, ), + compiled_block: nil, instructions: instructions, ip: ip, stack: stack, @@ -527,6 +534,7 @@ class Crystal::Repl::Interpreter %call_frame = CallFrame.new( compiled_def: {{compiled_def}}, + compiled_block: nil, instructions: {{compiled_def}}.instructions, ip: {{compiled_def}}.instructions.instructions.to_unsafe, # We need to adjust the call stack to start right @@ -562,6 +570,7 @@ class Crystal::Repl::Interpreter %block_caller_frame_index = @call_stack.last.block_caller_frame_index copied_call_frame = @call_stack[%block_caller_frame_index].copy_with( + compiled_block: {{compiled_block}}, instructions: {{compiled_block}}.instructions, ip: {{compiled_block}}.instructions.instructions.to_unsafe, stack: stack, @@ -1132,107 +1141,116 @@ class Crystal::Repl::Interpreter end private def pry(ip, instructions, stack_bottom, stack) - call_frame = @call_stack.last - compiled_def = call_frame.compiled_def - a_def = compiled_def.def - local_vars = compiled_def.local_vars offset = (ip - instructions.instructions.to_unsafe).to_i32 node = instructions.nodes[offset]? pry_node = @pry_node - if node && (location = node.location) && different_node_line?(node, pry_node) - whereami(a_def, location) - - # puts - # puts Slice.new(stack_bottom, stack - stack_bottom).hexdump - # puts - - # Remember the portion from stack_bottom + local_vars.max_bytesize up to stack - # because it might happen that the child interpreter will overwrite some - # of that if we already have some values in the stack past the local vars - data_size = stack - (stack_bottom + local_vars.max_bytesize) - data = Pointer(Void).malloc(data_size).as(UInt8*) - data.copy_from(stack_bottom + local_vars.max_bytesize, data_size) - - gatherer = LocalVarsGatherer.new(location, a_def) - gatherer.gather - meta_vars = gatherer.meta_vars - block_level = gatherer.block_level - - main_visitor = MainVisitor.new( - @context.program, - vars: meta_vars, - meta_vars: meta_vars, - typed_def: a_def) - main_visitor.scope = compiled_def.owner - main_visitor.path_lookup = compiled_def.owner # TODO: this is probably not right - - interpreter = Interpreter.new(self, compiled_def, stack_bottom, block_level) - - while @pry - # TODO: support multi-line expressions - - print "pry> " - line = gets - unless line - self.pry = false - break - end - case line - when "continue" - self.pry = false - break - when "step" - @pry_node = node - @pry_max_target_frame = nil - break - when "next" - @pry_node = node - @pry_max_target_frame = @call_stack.last.real_frame_index - break - when "finish" - @pry_node = node - @pry_max_target_frame = @call_stack.last.real_frame_index - 1 - break - when "whereami" - whereami(a_def, location) - next - when "*d" - puts compiled_def.local_vars - puts Disassembler.disassemble(@context, compiled_def) - next - when "*s" - puts Slice.new(@stack, stack - @stack).hexdump - next - end + return unless node - begin - parser = Parser.new( - line, - string_pool: @context.program.string_pool, - var_scopes: [interpreter.local_vars.names.to_set], - ) - line_node = parser.parse - - line_node = @context.program.normalize(line_node) - line_node = @context.program.semantic(line_node, main_visitor: main_visitor) - - value = interpreter.interpret(line_node, meta_vars) - puts value.to_s - rescue ex : Crystal::CodeError - ex.color = true - ex.error_trace = true - puts ex - next - rescue ex : Exception - ex.inspect_with_backtrace(STDOUT) - next - end + location = node.location + return unless location + + return unless different_node_line?(node, pry_node) + + call_frame = @call_stack.last + compiled_def = call_frame.compiled_def + compiled_block = call_frame.compiled_block + local_vars = compiled_block.try(&.local_vars) || compiled_def.local_vars + + a_def = compiled_def.def + + whereami(a_def, location) + + # puts + # puts Slice.new(stack_bottom, stack - stack_bottom).hexdump + # puts + + # Remember the portion from stack_bottom + local_vars.max_bytesize up to stack + # because it might happen that the child interpreter will overwrite some + # of that if we already have some values in the stack past the local vars + data_size = stack - (stack_bottom + local_vars.max_bytesize) + data = Pointer(Void).malloc(data_size).as(UInt8*) + data.copy_from(stack_bottom + local_vars.max_bytesize, data_size) + + gatherer = LocalVarsGatherer.new(location, a_def) + gatherer.gather + meta_vars = gatherer.meta_vars + block_level = local_vars.block_level + + main_visitor = MainVisitor.new( + @context.program, + vars: meta_vars, + meta_vars: meta_vars, + typed_def: a_def) + main_visitor.scope = compiled_def.owner + main_visitor.path_lookup = compiled_def.owner # TODO: this is probably not right + + interpreter = Interpreter.new(self, compiled_def, local_vars, stack_bottom, block_level) + + while @pry + # TODO: support multi-line expressions + + print "pry> " + line = gets + unless line + self.pry = false + break end - # Restore the stack data in case it tas overwritten - (stack_bottom + local_vars.max_bytesize).copy_from(data, data_size) + case line + when "continue" + self.pry = false + break + when "step" + @pry_node = node + @pry_max_target_frame = nil + break + when "next" + @pry_node = node + @pry_max_target_frame = @call_stack.last.real_frame_index + break + when "finish" + @pry_node = node + @pry_max_target_frame = @call_stack.last.real_frame_index - 1 + break + when "whereami" + whereami(a_def, location) + next + when "*d" + puts local_vars + puts Disassembler.disassemble(@context, compiled_block || compiled_def) + next + when "*s" + puts Slice.new(@stack, stack - @stack).hexdump + next + end + + begin + parser = Parser.new( + line, + string_pool: @context.program.string_pool, + var_scopes: [meta_vars.keys.to_set], + ) + line_node = parser.parse + + line_node = @context.program.normalize(line_node) + line_node = @context.program.semantic(line_node, main_visitor: main_visitor) + + value = interpreter.interpret(line_node, meta_vars) + puts value.to_s + rescue ex : Crystal::CodeError + ex.color = true + ex.error_trace = true + puts ex + next + rescue ex : Exception + ex.inspect_with_backtrace(STDOUT) + next + end end + + # Restore the stack data in case it tas overwritten + (stack_bottom + local_vars.max_bytesize).copy_from(data, data_size) end private def whereami(a_def : Def, location : Location) diff --git a/src/compiler/crystal/interpreter/local_vars.cr b/src/compiler/crystal/interpreter/local_vars.cr index 8fc480195bbc..d8637a7f2264 100644 --- a/src/compiler/crystal/interpreter/local_vars.cr +++ b/src/compiler/crystal/interpreter/local_vars.cr @@ -27,6 +27,8 @@ require "./repl" class Crystal::Repl::LocalVars record Key, name : String, block_level : Int32 + getter block_level : Int32 + def initialize(@context : Context) @types = {} of Key => Type @name_to_index = {} of Key => Int32 diff --git a/src/compiler/crystal/interpreter/local_vars_gatherer.cr b/src/compiler/crystal/interpreter/local_vars_gatherer.cr index 58a2f4af94ad..3028346e78a9 100644 --- a/src/compiler/crystal/interpreter/local_vars_gatherer.cr +++ b/src/compiler/crystal/interpreter/local_vars_gatherer.cr @@ -32,12 +32,8 @@ class Crystal::Repl::LocalVarsGatherer < Crystal::Visitor # in the `initialize` method. getter meta_vars : MetaVars - # What's the block level the `debugger` is currently in. - getter block_level : Int32 - def initialize(@location : Location, @def : Def) @meta_vars = MetaVars.new - @block_level = 0 end def gather : Nil @@ -91,7 +87,6 @@ class Crystal::Repl::LocalVarsGatherer < Crystal::Visitor old_block = @block @block = node - @block_level += 1 node.args.each &.accept self node.body.accept self