1use super::core::{
7 ContinueResult, DebugState, DebugStatistics, DebugValue, DebuggerConfig,
8 DisassemblyInstruction, DisassemblyView, EvaluationResult, ExecutionLocation, ExecutionState,
9 ExecutionStep, InspectionResult, InspectionTarget, InstructionExecutionResult, MemoryView,
10 NodeExecutionResult, NodeMetadata, StepResult, TypeInfo,
11};
12use super::execution::DebugExecutionEngine;
13use super::state::{CallStack, MemoryState};
14use crate::{ir::IrModule, ComputationGraph, JitError, JitResult, NodeId};
15use std::collections::HashMap;
16use std::time::SystemTime;
17use torsh_core::{DType, Shape};
18
19pub struct DebugSession {
21 graph: Option<ComputationGraph>,
22 ir_module: Option<IrModule>,
23 current_location: ExecutionLocation,
24 execution_state: ExecutionState,
25 execution_trace: Vec<ExecutionStep>,
26 call_stack: CallStack,
27 variable_bindings: HashMap<String, DebugValue>,
28 memory_state: MemoryState,
29 statistics: DebugStatistics,
30 config: DebuggerConfig,
31 execution_engine: DebugExecutionEngine,
32}
33
34impl DebugSession {
35 pub fn new(graph: ComputationGraph, config: DebuggerConfig) -> Self {
41 let initial_location = ExecutionLocation::GraphNode(
42 graph
43 .nodes()
44 .next()
45 .map(|(id, _)| id)
46 .unwrap_or(NodeId::new(0)),
47 );
48
49 let execution_engine = DebugExecutionEngine::new(config.clone());
50
51 Self {
52 graph: Some(graph),
53 ir_module: None,
54 current_location: initial_location,
55 execution_state: ExecutionState::new(),
56 execution_trace: Vec::new(),
57 call_stack: CallStack::new(),
58 variable_bindings: HashMap::new(),
59 memory_state: MemoryState::new(),
60 statistics: DebugStatistics::new(),
61 config,
62 execution_engine,
63 }
64 }
65
66 pub fn from_ir(ir_module: IrModule, config: DebuggerConfig) -> Self {
72 let initial_location = ExecutionLocation::Instruction {
73 function: "main".to_string(),
74 instruction_index: 0,
75 };
76
77 let execution_engine = DebugExecutionEngine::new(config.clone());
78
79 Self {
80 graph: None,
81 ir_module: Some(ir_module),
82 current_location: initial_location,
83 execution_state: ExecutionState::new(),
84 execution_trace: Vec::new(),
85 call_stack: CallStack::new(),
86 variable_bindings: HashMap::new(),
87 memory_state: MemoryState::new(),
88 statistics: DebugStatistics::new(),
89 config,
90 execution_engine,
91 }
92 }
93
94 pub fn step(&mut self) -> JitResult<StepResult> {
99 if self.is_execution_complete() {
100 return Err(JitError::AnalysisError(
101 "Execution already completed".to_string(),
102 ));
103 }
104
105 let step_start = std::time::Instant::now();
106
107 let result = match self.current_location.clone() {
108 ExecutionLocation::GraphNode(node_id) => self.step_graph_node(node_id),
109 ExecutionLocation::Instruction {
110 function,
111 instruction_index,
112 } => self.step_ir_instruction(&function, instruction_index),
113 ExecutionLocation::Completed => {
114 return Err(JitError::AnalysisError(
115 "Execution already completed".to_string(),
116 ));
117 }
118 };
119
120 let step_duration = step_start.elapsed();
121 self.statistics.total_steps += 1;
122 self.statistics.total_execution_time += step_duration;
123
124 match result {
125 Ok(_) => {
126 if self.is_execution_complete() {
127 Ok(StepResult::Completed)
128 } else {
129 Ok(StepResult::Success)
130 }
131 }
132 Err(e) => Err(e),
133 }
134 }
135
136 pub fn step_over(&mut self) -> JitResult<StepResult> {
141 let current_call_depth = self.call_stack.depth();
142
143 loop {
144 let result = self.step()?;
145
146 match result {
147 StepResult::Completed => return Ok(StepResult::Completed),
148 StepResult::Success => {
149 if self.call_stack.depth() <= current_call_depth {
151 break;
152 }
153 }
154 }
155 }
156
157 Ok(StepResult::Success)
158 }
159
160 pub fn step_into(&mut self) -> JitResult<StepResult> {
165 self.step()
167 }
168
169 pub fn step_out(&mut self) -> JitResult<StepResult> {
174 let target_depth = self.call_stack.depth().saturating_sub(1);
175
176 while self.call_stack.depth() > target_depth {
177 let result = self.step()?;
178 if matches!(result, StepResult::Completed) {
179 return Ok(StepResult::Completed);
180 }
181 }
182
183 Ok(StepResult::Success)
184 }
185
186 pub fn continue_execution(&mut self) -> JitResult<ContinueResult> {
191 loop {
192 if self.should_break_at_current_location() {
194 return Ok(ContinueResult::Breakpoint);
195 }
196
197 match self.step() {
199 Ok(StepResult::Success) => {
200 if self.is_execution_complete() {
202 return Ok(ContinueResult::Completed);
203 }
204 }
205 Ok(StepResult::Completed) => {
206 return Ok(ContinueResult::Completed);
207 }
208 Err(e) => return Err(e),
209 }
210 }
211 }
212
213 fn step_graph_node(&mut self, node_id: NodeId) -> JitResult<()> {
215 if let Some(graph) = &self.graph {
216 if let Some(node) = graph.node(node_id) {
217 let step = ExecutionStep {
219 location: self.current_location.clone(),
220 timestamp: SystemTime::now(),
221 operation: node.operation_type().to_string(),
222 inputs: self.get_node_inputs(node_id),
223 outputs: Vec::new(), state_changes: HashMap::new(),
225 };
226
227 let result = self
229 .execution_engine
230 .execute_node_debug(node, graph, node_id)?;
231
232 self.variable_bindings.insert(
234 format!("node_{:?}", node_id),
235 DebugValue::Tensor {
236 data: result.data.clone(),
237 shape: result.shape.clone(),
238 dtype: result.dtype,
239 },
240 );
241
242 self.advance_to_next_graph_node(node_id)?;
244
245 let mut completed_step = step;
247 completed_step.outputs = vec![result];
248 self.execution_trace.push(completed_step);
249 }
250 }
251
252 Ok(())
253 }
254
255 fn step_ir_instruction(&mut self, function: &str, instruction_index: usize) -> JitResult<()> {
257 if let Some(ir_module) = self.ir_module.clone() {
258 if let Some(func) = ir_module.get_function(function) {
259 if let Some(instruction) = func.instructions().get(instruction_index) {
260 let step = ExecutionStep {
262 location: self.current_location.clone(),
263 timestamp: SystemTime::now(),
264 operation: format!("{:?}", instruction),
265 inputs: self.get_instruction_inputs(instruction),
266 outputs: Vec::new(),
267 state_changes: HashMap::new(),
268 };
269
270 let result = self.execution_engine.execute_instruction_debug(
272 instruction,
273 &ir_module,
274 &mut self.execution_state,
275 )?;
276
277 self.update_execution_state_from_instruction(instruction, result)?;
279
280 self.advance_to_next_instruction(function, instruction_index)?;
282
283 self.execution_trace.push(step);
285 }
286 }
287 }
288
289 Ok(())
290 }
291
292 fn get_node_inputs(&self, node_id: NodeId) -> Vec<NodeExecutionResult> {
294 if let Some(graph) = &self.graph {
296 let inputs = graph.get_node_inputs(node_id);
297 inputs
298 .iter()
299 .map(|&input_id| {
300 let var_name = format!("node_{:?}", input_id);
302 if let Some(DebugValue::Tensor { data, shape, dtype }) =
303 self.variable_bindings.get(&var_name)
304 {
305 NodeExecutionResult {
306 data: data.clone(),
307 shape: shape.clone(),
308 dtype: *dtype,
309 }
310 } else {
311 NodeExecutionResult {
313 data: vec![1.0, 2.0, 3.0],
314 shape: Shape::new(vec![3]),
315 dtype: DType::F32,
316 }
317 }
318 })
319 .collect()
320 } else {
321 Vec::new()
322 }
323 }
324
325 fn get_instruction_inputs(
327 &self,
328 instruction: &crate::ir::Instruction,
329 ) -> Vec<NodeExecutionResult> {
330 Vec::new()
332 }
333
334 fn advance_to_next_graph_node(&mut self, current_node: NodeId) -> JitResult<()> {
336 if let Some(graph) = &self.graph {
337 let successors: Vec<_> = graph
339 .edges()
340 .filter(|(source, _target, _edge)| *source == current_node)
341 .map(|(_source, target, _edge)| target)
342 .collect();
343
344 if let Some(next_node) = successors.first() {
345 self.current_location = ExecutionLocation::GraphNode(*next_node);
346 } else {
347 self.current_location = ExecutionLocation::Completed;
349 }
350 }
351 Ok(())
352 }
353
354 fn advance_to_next_instruction(
356 &mut self,
357 function: &str,
358 current_index: usize,
359 ) -> JitResult<()> {
360 if let Some(ir_module) = &self.ir_module {
361 if let Some(func) = ir_module.get_function(function) {
362 let next_index = current_index + 1;
363 if next_index < func.instructions().len() {
364 self.current_location = ExecutionLocation::Instruction {
365 function: function.to_string(),
366 instruction_index: next_index,
367 };
368 } else {
369 if !self.call_stack.is_empty() {
371 self.current_location = self.call_stack.pop();
372 } else {
373 self.current_location = ExecutionLocation::Completed;
374 }
375 }
376 }
377 }
378 Ok(())
379 }
380
381 fn update_execution_state_from_instruction(
383 &mut self,
384 instruction: &crate::ir::Instruction,
385 result: InstructionExecutionResult,
386 ) -> JitResult<()> {
387 match result {
388 InstructionExecutionResult::Value(value) => {
389 let var_name = format!("temp_{}", self.execution_trace.len());
391 self.variable_bindings.insert(var_name, value);
392 }
393 InstructionExecutionResult::SideEffect => {
394 }
397 InstructionExecutionResult::Return => {
398 if !self.call_stack.is_empty() {
400 let return_location = self.call_stack.pop();
401 self.current_location = return_location;
402 } else {
403 self.current_location = ExecutionLocation::Completed;
404 }
405 }
406 InstructionExecutionResult::NoOp => {
407 }
409 }
410 Ok(())
411 }
412
413 fn should_break_at_current_location(&self) -> bool {
426 false
429 }
430
431 pub fn is_execution_complete(&self) -> bool {
433 matches!(self.current_location, ExecutionLocation::Completed)
434 }
435
436 pub fn inspect_target(&self, target: &InspectionTarget) -> JitResult<InspectionResult> {
444 match target {
445 InspectionTarget::Variable(name) => {
446 if let Some(value) = self.variable_bindings.get(name) {
447 Ok(InspectionResult::Variable {
448 name: name.clone(),
449 value: value.clone(),
450 type_info: self.get_type_info_for_value(value),
451 })
452 } else {
453 Err(JitError::RuntimeError(format!(
454 "Variable '{}' not found",
455 name
456 )))
457 }
458 }
459 InspectionTarget::Node(node_id) => {
460 let var_name = format!("node_{:?}", node_id);
461 if let Some(value) = self.variable_bindings.get(&var_name) {
462 Ok(InspectionResult::Node {
463 node_id: *node_id,
464 value: value.clone(),
465 metadata: self.get_node_metadata(*node_id),
466 })
467 } else {
468 Err(JitError::RuntimeError(format!(
469 "Node {:?} not executed yet",
470 node_id
471 )))
472 }
473 }
474 InspectionTarget::Memory(address) => {
475 let memory_content = self.memory_state.read_memory(*address, 16)?;
476 Ok(InspectionResult::Memory {
477 address: *address,
478 content: memory_content,
479 size: 16,
480 })
481 }
482 }
483 }
484
485 fn get_type_info_for_value(&self, value: &DebugValue) -> TypeInfo {
487 match value {
488 DebugValue::Scalar(_) => TypeInfo {
489 type_name: "f64".to_string(),
490 size_bytes: 8,
491 alignment: 8,
492 },
493 DebugValue::Integer(_) => TypeInfo {
494 type_name: "i64".to_string(),
495 size_bytes: 8,
496 alignment: 8,
497 },
498 DebugValue::Boolean(_) => TypeInfo {
499 type_name: "bool".to_string(),
500 size_bytes: 1,
501 alignment: 1,
502 },
503 DebugValue::Tensor { dtype, shape, .. } => TypeInfo {
504 type_name: format!("Tensor<{:?}>", dtype),
505 size_bytes: shape.size(0).unwrap_or(1) * dtype.size_bytes(),
506 alignment: dtype.size_bytes(),
507 },
508 }
509 }
510
511 fn get_node_metadata(&self, node_id: NodeId) -> NodeMetadata {
513 if let Some(graph) = &self.graph {
514 if let Some(node) = graph.node(node_id) {
515 let input_count = graph.get_node_inputs(node_id).len();
516 return NodeMetadata {
517 operation: node.operation_type().to_string(),
518 input_count,
519 output_shape: node.output_shape.clone(),
520 dtype: node.dtype,
521 };
522 }
523 }
524
525 NodeMetadata {
526 operation: "unknown".to_string(),
527 input_count: 0,
528 output_shape: Shape::new(vec![]),
529 dtype: DType::F32,
530 }
531 }
532
533 pub fn evaluate_expression(&self, expression: &str) -> JitResult<EvaluationResult> {
541 if let Some(value) = self.variable_bindings.get(expression) {
543 Ok(EvaluationResult {
544 expression: expression.to_string(),
545 result: value.clone(),
546 success: true,
547 error_message: None,
548 })
549 } else if expression.starts_with("node_") {
550 if let Ok(node_index) = expression[5..].parse::<usize>() {
552 let var_name = format!("node_{}", node_index);
553 if let Some(value) = self.variable_bindings.get(&var_name) {
554 Ok(EvaluationResult {
555 expression: expression.to_string(),
556 result: value.clone(),
557 success: true,
558 error_message: None,
559 })
560 } else {
561 Ok(EvaluationResult {
562 expression: expression.to_string(),
563 result: DebugValue::Scalar(0.0),
564 success: false,
565 error_message: Some("Node not executed yet".to_string()),
566 })
567 }
568 } else {
569 Ok(EvaluationResult {
570 expression: expression.to_string(),
571 result: DebugValue::Scalar(0.0),
572 success: false,
573 error_message: Some("Invalid node reference".to_string()),
574 })
575 }
576 } else {
577 if let Ok(value) = expression.parse::<i64>() {
579 Ok(EvaluationResult {
580 expression: expression.to_string(),
581 result: DebugValue::Integer(value),
582 success: true,
583 error_message: None,
584 })
585 } else if let Ok(value) = expression.parse::<f64>() {
586 Ok(EvaluationResult {
587 expression: expression.to_string(),
588 result: DebugValue::Scalar(value),
589 success: true,
590 error_message: None,
591 })
592 } else if let Ok(value) = expression.parse::<bool>() {
593 Ok(EvaluationResult {
594 expression: expression.to_string(),
595 result: DebugValue::Boolean(value),
596 success: true,
597 error_message: None,
598 })
599 } else {
600 Ok(EvaluationResult {
601 expression: expression.to_string(),
602 result: DebugValue::Scalar(0.0),
603 success: false,
604 error_message: Some("Expression not found or invalid".to_string()),
605 })
606 }
607 }
608 }
609
610 pub fn get_current_state(&self) -> DebugState {
612 DebugState {
613 location: self.current_location.clone(),
614 call_stack: self.call_stack.clone(),
615 variables: self.variable_bindings.clone(),
616 execution_step: self.execution_trace.len(),
617 is_running: !self.is_execution_complete(),
618 }
619 }
620
621 pub fn get_execution_trace(&self) -> Vec<ExecutionStep> {
623 self.execution_trace.clone()
624 }
625
626 pub fn get_call_stack(&self) -> CallStack {
628 self.call_stack.clone()
629 }
630
631 pub fn get_local_variables(&self) -> HashMap<String, DebugValue> {
633 self.variable_bindings.clone()
635 }
636
637 pub fn get_memory_view(&self, address: u64) -> JitResult<MemoryView> {
639 let content = self.memory_state.read_memory(address, 64)?;
640 Ok(MemoryView {
641 start_address: address,
642 content,
643 size: 64,
644 })
645 }
646
647 pub fn disassemble_at(&self, location: ExecutionLocation) -> JitResult<DisassemblyView> {
649 match location {
650 ExecutionLocation::GraphNode(node_id) => {
651 if let Some(graph) = &self.graph {
652 if let Some(node) = graph.node(node_id) {
653 Ok(DisassemblyView {
654 location,
655 instructions: vec![DisassemblyInstruction {
656 address: node_id.index() as u64,
657 opcode: node.operation_type().to_string(),
658 operands: format!(
659 "inputs: {}",
660 graph.get_node_inputs(node_id).len()
661 ),
662 comment: Some(format!("Output shape: {:?}", node.output_shape)),
663 }],
664 })
665 } else {
666 Err(JitError::RuntimeError("Node not found".to_string()))
667 }
668 } else {
669 Err(JitError::RuntimeError("No graph available".to_string()))
670 }
671 }
672 ExecutionLocation::Instruction {
673 ref function,
674 instruction_index,
675 } => {
676 if let Some(ir_module) = &self.ir_module {
677 if let Some(func) = ir_module.get_function(function) {
678 if let Some(instruction) = func.instructions().get(instruction_index) {
679 Ok(DisassemblyView {
680 location,
681 instructions: vec![DisassemblyInstruction {
682 address: instruction_index as u64,
683 opcode: format!("{:?}", instruction.opcode),
684 operands: format!("operands: {}", instruction.operands.len()),
685 comment: Some(format!("result: {:?}", instruction.result)),
686 }],
687 })
688 } else {
689 Err(JitError::RuntimeError("Instruction not found".to_string()))
690 }
691 } else {
692 Err(JitError::RuntimeError("Function not found".to_string()))
693 }
694 } else {
695 Err(JitError::RuntimeError("No IR module available".to_string()))
696 }
697 }
698 ExecutionLocation::Completed => {
699 Err(JitError::RuntimeError("Execution completed".to_string()))
700 }
701 }
702 }
703
704 pub fn get_statistics(&self) -> DebugStatistics {
706 let mut stats = self.statistics.clone();
708 let engine_stats = self.execution_engine.get_statistics();
709
710 stats.total_steps = stats.total_steps.max(engine_stats.total_steps);
711 stats.total_execution_time = stats
712 .total_execution_time
713 .max(engine_stats.total_execution_time);
714
715 stats
716 }
717
718 pub fn reset(&mut self) {
720 self.execution_trace.clear();
721 self.call_stack.clear();
722 self.variable_bindings.clear();
723 self.memory_state.clear();
724 self.statistics = DebugStatistics::new();
725 self.execution_state = ExecutionState::new();
726
727 if let Some(graph) = &self.graph {
729 self.current_location = ExecutionLocation::GraphNode(
730 graph
731 .nodes()
732 .next()
733 .map(|(id, _)| id)
734 .unwrap_or(NodeId::new(0)),
735 );
736 } else if self.ir_module.is_some() {
737 self.current_location = ExecutionLocation::Instruction {
738 function: "main".to_string(),
739 instruction_index: 0,
740 };
741 } else {
742 self.current_location = ExecutionLocation::Completed;
743 }
744 }
745
746 pub fn config(&self) -> &DebuggerConfig {
748 &self.config
749 }
750
751 pub fn update_config(&mut self, config: DebuggerConfig) {
753 self.config = config.clone();
754 self.execution_engine.update_config(config);
755 }
756
757 pub fn set_variable(&mut self, name: String, value: DebugValue) {
759 self.variable_bindings.insert(name, value);
760 }
761
762 pub fn get_variable(&self, name: &str) -> Option<&DebugValue> {
764 self.variable_bindings.get(name)
765 }
766
767 pub fn get_execution_engine_statistics(
769 &self,
770 ) -> &std::collections::HashMap<String, super::execution::OperationStatistics> {
771 self.execution_engine.get_operation_statistics()
772 }
773
774 pub fn memory_state(&self) -> &MemoryState {
776 &self.memory_state
777 }
778
779 pub fn memory_state_mut(&mut self) -> &mut MemoryState {
781 &mut self.memory_state
782 }
783}
784
785impl super::watch::ExpressionEvaluator for DebugSession {
787 fn evaluate_expression(&self, expression: &str) -> JitResult<EvaluationResult> {
788 self.evaluate_expression(expression)
789 }
790}
791
792#[cfg(test)]
793mod tests {
794 use super::*;
795
796 fn create_test_session() -> DebugSession {
797 let config = DebuggerConfig::default();
798 let graph = ComputationGraph::new(); DebugSession::new(graph, config)
801 }
802
803 #[test]
804 fn test_session_creation() {
805 let session = create_test_session();
806 assert!(!session.is_execution_complete());
807 assert_eq!(session.execution_trace.len(), 0);
808 assert_eq!(session.variable_bindings.len(), 0);
809 }
810
811 #[test]
812 fn test_variable_management() {
813 let mut session = create_test_session();
814
815 let value = DebugValue::Scalar(42.0);
816 session.set_variable("test_var".to_string(), value.clone());
817
818 assert_eq!(session.get_variable("test_var"), Some(&value));
819 assert_eq!(session.get_variable("nonexistent"), None);
820 }
821
822 #[test]
823 fn test_expression_evaluation() {
824 let mut session = create_test_session();
825
826 let result = session
828 .evaluate_expression("42.5")
829 .expect("expression evaluation should succeed");
830 assert!(result.success);
831 assert!(matches!(result.result, DebugValue::Scalar(42.5)));
832
833 let result = session
834 .evaluate_expression("123")
835 .expect("expression evaluation should succeed");
836 assert!(result.success);
837 assert!(matches!(result.result, DebugValue::Integer(123)));
838
839 let result = session
840 .evaluate_expression("true")
841 .expect("expression evaluation should succeed");
842 assert!(result.success);
843 assert!(matches!(result.result, DebugValue::Boolean(true)));
844
845 session.set_variable("x".to_string(), DebugValue::Scalar(3.14));
847 let result = session
848 .evaluate_expression("x")
849 .expect("expression evaluation should succeed");
850 assert!(result.success);
851 assert!(matches!(result.result, DebugValue::Scalar(3.14)));
852
853 let result = session
855 .evaluate_expression("unknown_var")
856 .expect("expression evaluation should succeed");
857 assert!(!result.success);
858 assert!(result.error_message.is_some());
859 }
860
861 #[test]
862 fn test_execution_state() {
863 let session = create_test_session();
864 let state = session.get_current_state();
865
866 assert!(state.is_running);
867 assert_eq!(state.execution_step, 0);
868 assert!(state.call_stack.is_empty());
869 assert!(state.variables.is_empty());
870 }
871
872 #[test]
873 fn test_memory_operations() {
874 let mut session = create_test_session();
875
876 let memory = session.memory_state_mut();
877 memory
878 .write_memory(0x1000, &[1, 2, 3, 4])
879 .expect("memory write should succeed");
880
881 let memory_view = session
882 .get_memory_view(0x1000)
883 .expect("memory view should be accessible");
884 assert_eq!(memory_view.start_address, 0x1000);
885 assert_eq!(&memory_view.content[0..4], &[1, 2, 3, 4]);
886 }
887
888 #[test]
889 fn test_session_reset() {
890 let mut session = create_test_session();
891
892 session.set_variable("test".to_string(), DebugValue::Scalar(1.0));
894 session.statistics.total_steps = 10;
895
896 session.reset();
898
899 assert_eq!(session.variable_bindings.len(), 0);
900 assert_eq!(session.execution_trace.len(), 0);
901 assert_eq!(session.statistics.total_steps, 0);
902 assert!(session.call_stack.is_empty());
903 }
904
905 #[test]
906 fn test_configuration_update() {
907 let mut session = create_test_session();
908
909 let mut new_config = DebuggerConfig::default();
910 new_config.max_trace_length = 5000;
911
912 session.update_config(new_config.clone());
913 assert_eq!(session.config().max_trace_length, 5000);
914 }
915
916 #[test]
917 fn test_inspection_targets() {
918 let mut session = create_test_session();
919
920 session.set_variable("test_var".to_string(), DebugValue::Scalar(42.0));
922 let result = session.inspect_target(&InspectionTarget::Variable("test_var".to_string()));
923 assert!(result.is_ok());
924
925 session
927 .memory_state_mut()
928 .write_memory(0x1000, &[1, 2, 3, 4])
929 .expect("operation should succeed");
930 let result = session.inspect_target(&InspectionTarget::Memory(0x1000));
931 assert!(result.is_ok());
932
933 let result = session.inspect_target(&InspectionTarget::Variable("unknown".to_string()));
935 assert!(result.is_err());
936 }
937}