diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1de5cc8e..8693df1c 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -97,6 +97,11 @@ jobs: run: | echo "Test result on ${{matrix.os}} with LLVM ${{matrix.llvm}}" > result.md bash .github/workflows/test_llgo.sh + + - name: LLDB tests + run: | + echo "Test lldb with llgo plugin on ${{matrix.os}} with LLVM ${{matrix.llvm}}" + bash _lldb/runtest.sh - name: Test demos continue-on-error: true diff --git a/_lldb/README.md b/_lldb/README.md new file mode 100644 index 00000000..1f6a4946 --- /dev/null +++ b/_lldb/README.md @@ -0,0 +1,115 @@ +## LLGo Plugin of LLDB + +### Build with debug info + +```shell +LLGO_DEBUG=1 llgo build -o cl/_testdata/debug/out ./cl/_testdata/debug +``` + +### Debug with lldb + +```shell +_lldb/runlldb.sh ./cl/_testdata/debug/out +``` + +or + +```shell +/opt/homebrew/bin/lldb -O "command script import _lldb/llgo_plugin.py" ./cl/_testdata/debug/out +# github.com/goplus/llgo/cl/_testdata/debug +Breakpoint 1: no locations (pending). +Breakpoint set in dummy target, will get copied into future targets. +(lldb) command script import _lldb/llgo_plugin.py +(lldb) target create "./cl/_testdata/debug/out" +Current executable set to '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64). +(lldb) r +Process 21992 launched: '/Users/lijie/source/goplus/llgo/cl/_testdata/debug/out' (arm64) +globalInt: 301 +s: 0x100123e40 +0x100123be0 +5 8 +called function with struct +1 2 3 4 5 6 7 8 9 10 +1.100000e+01 +1.200000e+01 true (+1.300000e+01+1.400000e+01i) (+1.500000e+01+1.600000e+01i) [3/3]0x1001129a0 [3/3]0x100112920 hello 0x1001149b0 0x100123ab0 0x100123d10 0x1001149e0 (0x100116810,0x1001149d0) 0x10011bf00 0x10010fa80 (0x100116840,0x100112940) 0x10001b4a4 +9 +1 (0x1001167e0,0x100112900) +called function with types +0x100123e40 +0x1000343d0 +Process 21992 stopped +* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 + frame #0: 0x000000010001b3b4 out`main at in.go:225:12 + 222 // s.i8: '\x01' + 223 // s.i16: 2 + 224 s.i8 = 0x12 +-> 225 println(s.i8) + 226 // Expected: + 227 // all variables: globalInt globalStruct globalStructPtr s i err + 228 // s.i8: '\x12' +(lldb) v +var i int = +var s github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = { + i8 = '\x12', + i16 = 2, + i32 = 3, + i64 = 4, + i = 5, + u8 = '\x06', + u16 = 7, + u32 = 8, + u64 = 9, + u = 10, + f32 = 11, + f64 = 12, + b = true, + c64 = {real = 13, imag = 14}, + c128 = {real = 15, imag = 16}, + slice = []int{21, 22, 23}, + arr = [3]int{24, 25, 26}, + arr2 = [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}}, + s = "hello", + e = {i = 30}, + pf = 0x0000000100123d10, + pi = 0x00000001001149e0, + intr = {type = 0x0000000100116810, data = 0x00000001001149d0}, + m = {count = 4296130304}, + c = {}, + err = {type = 0x0000000100116840, data = 0x0000000100112940}, + fn = {f = 0x000000010001b4a4, data = 0x00000001001149c0}, + pad1 = 100, + pad2 = 200 +} +var globalStructPtr *github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = +var globalStruct github.com/goplus/llgo/cl/_testdata/debug.StructWithAllTypeFields = { + i8 = '\x01', + i16 = 2, + i32 = 3, + i64 = 4, + i = 5, + u8 = '\x06', + u16 = 7, + u32 = 8, + u64 = 9, + u = 10, + f32 = 11, + f64 = 12, + b = true, + c64 = {real = 13, imag = 14}, + c128 = {real = 15, imag = 16}, + slice = []int{21, 22, 23}, + arr = [3]int{24, 25, 26}, + arr2 = [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}}, + s = "hello", + e = {i = 30}, + pf = 0x0000000100123d10, + pi = 0x00000001001149e0, + intr = {type = 0x0000000100116810, data = 0x00000001001149d0}, + m = {count = 4296130304}, + c = {}, + err = {type = 0x0000000100116840, data = 0x0000000100112940}, + fn = {f = 0x000000010001b4a4, data = 0x00000001001149c0}, + pad1 = 100, + pad2 = 200 +} +var globalInt int = 301 +var err error = {type = 0x0000000100112900, data = 0x000000000000001a} +``` diff --git a/_lldb/common.sh b/_lldb/common.sh new file mode 100644 index 00000000..94dfe334 --- /dev/null +++ b/_lldb/common.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Function to find LLDB 18+ +find_lldb() { + local lldb_paths=( + "/opt/homebrew/bin/lldb" + "/usr/local/bin/lldb" + "/usr/bin/lldb" + "lldb" # This will use the system PATH + ) + + for lldb_path in "${lldb_paths[@]}"; do + if command -v "$lldb_path" >/dev/null 2>&1; then + local version + version=$("$lldb_path" --version | grep -oE '[0-9]+' | head -1) + if [ "$version" -ge 18 ]; then + echo "$lldb_path" + return 0 + fi + fi + done + + echo "Error: LLDB 18 or higher not found" >&2 + exit 1 +} + +# Find LLDB 18+ +LLDB_PATH=$(find_lldb) +export LLDB_PATH + +# Default package path +export DEFAULT_PACKAGE_PATH="./cl/_testdata/debug" + +# Function to build the project +build_project() { + local package_path="$1" + LLGO_DEBUG=1 go run ./cmd/llgo build -o "${package_path}/out" "${package_path}" +} diff --git a/_lldb/llgo_plugin.py b/_lldb/llgo_plugin.py new file mode 100644 index 00000000..d45a1a5d --- /dev/null +++ b/_lldb/llgo_plugin.py @@ -0,0 +1,246 @@ +# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring + +import re +import lldb + + +def __lldb_init_module(debugger, _): + debugger.HandleCommand( + 'command script add -f llgo_plugin.print_go_expression p') + debugger.HandleCommand( + 'command script add -f llgo_plugin.print_all_variables v') + + +def is_llgo_compiler(target): + return True + module = target.GetModuleAtIndex(0) + + # Check for specific sections or symbols that might be unique to LLGo + llgo_indicators = ["__llgo_", "runtime.llgo", "llgo."] + + # Check sections + for i in range(module.GetNumSections()): + section = module.GetSectionAtIndex(i) + section_name = section.GetName() + if any(indicator in section_name for indicator in llgo_indicators): + return True + + # Check symbols + for symbol in module.symbols: + symbol_name = symbol.GetName() + if any(indicator in symbol_name for indicator in llgo_indicators): + return True + + # Check compile units + for i in range(module.GetNumCompileUnits()): + cu = module.GetCompileUnitAtIndex(i) + cu_name = cu.GetFileSpec().GetFilename() + print(f"Compile unit: {cu_name}") + # You can add more checks here if needed + + print("LLGo Compiler not detected") + return False + + +def print_go_expression(debugger, command, result, _internal_dict): + target = debugger.GetSelectedTarget() + if not is_llgo_compiler(target): + result.AppendMessage("Not a LLGo compiled binary.") + return + + frame = debugger.GetSelectedTarget().GetProcess( + ).GetSelectedThread().GetSelectedFrame() + + # Handle Go-style pointer member access + command = re.sub(r'(\w+)\.(\w+)', lambda m: f'(*{m.group(1)}).{m.group( + 2)}' if is_pointer(frame, m.group(1)) else m.group(0), command) + + var = frame.EvaluateExpression(command) + + if var.error.Success(): + formatted = format_value(var, debugger) + result.AppendMessage(formatted) + else: + result.AppendMessage(f"Error: {var.error}") + + +def print_all_variables(debugger, command, result, _internal_dict): + target = debugger.GetSelectedTarget() + if not is_llgo_compiler(target): + result.AppendMessage("Not a LLGo compiled binary.") + return + + frame = debugger.GetSelectedTarget().GetProcess( + ).GetSelectedThread().GetSelectedFrame() + variables = frame.GetVariables(True, True, True, False) + + output = [] + for var in variables: + type_name = map_type_name(var.GetType().GetName()) + formatted = format_value(var, debugger, include_type=False, indent=0) + output.append(f"var {var.GetName()} {type_name} = {formatted}") + + result.AppendMessage("\n".join(output)) + + +def is_pointer(frame, var_name): + var = frame.FindVariable(var_name) + return var.IsValid() and var.GetType().IsPointerType() + +# Format functions extracted from main.py + + +def format_value(var, debugger, include_type=True, indent=0): + if not var.IsValid(): + return "" + + var_type = var.GetType() + type_class = var_type.GetTypeClass() + type_name = map_type_name(var_type.GetName()) + + if var_type.IsPointerType(): + return format_pointer(var, debugger, indent, type_name) + + if type_name.startswith('[]'): # Slice + return format_slice(var, debugger, indent) + elif var_type.IsArrayType(): + return format_array(var, debugger, indent) + elif type_name == 'string': # String + return format_string(var) + elif type_class in [lldb.eTypeClassStruct, lldb.eTypeClassClass]: + return format_struct(var, debugger, include_type, indent, type_name) + else: + value = var.GetValue() + summary = var.GetSummary() + if value is not None: + return f"{value}" if include_type else str(value) + elif summary is not None: + return f"{summary}" if include_type else summary + else: + return "" + + +def format_slice(var, debugger, indent): + length = int(var.GetChildMemberWithName('len').GetValue()) + data_ptr = var.GetChildMemberWithName('data') + elements = [] + + ptr_value = int(data_ptr.GetValue(), 16) + element_type = data_ptr.GetType().GetPointeeType() + element_size = element_type.GetByteSize() + + target = debugger.GetSelectedTarget() + indent_str = ' ' * indent + next_indent_str = ' ' * (indent + 1) + + for i in range(length): + element_address = ptr_value + i * element_size + element = target.CreateValueFromAddress( + f"element_{i}", lldb.SBAddress(element_address, target), element_type) + value = format_value( + element, debugger, include_type=False, indent=indent+1) + elements.append(value) + + type_name = var.GetType().GetName() + + if len(elements) > 5: # 如果元素数量大于5,则进行折行显示 + result = f"{type_name}{{\n{next_indent_str}" + \ + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}" + else: + result = f"{type_name}{{{', '.join(elements)}}}" + + return result + + +def format_array(var, debugger, indent): + elements = [] + indent_str = ' ' * indent + next_indent_str = ' ' * (indent + 1) + + for i in range(var.GetNumChildren()): + value = format_value(var.GetChildAtIndex( + i), debugger, include_type=False, indent=indent+1) + elements.append(value) + + array_size = var.GetNumChildren() + element_type = map_type_name(var.GetType().GetArrayElementType().GetName()) + type_name = f"[{array_size}]{element_type}" + + if len(elements) > 5: # 如果元素数量大于5,则进行折行显示 + return f"{type_name}{{\n{next_indent_str}" + f",\n{next_indent_str}".join(elements) + f"\n{indent_str}}}" + else: + return f"{type_name}{{{', '.join(elements)}}}" + + +def format_string(var): + summary = var.GetSummary() + if summary is not None: + return summary # Keep the quotes + else: + data = var.GetChildMemberWithName('data').GetValue() + length = int(var.GetChildMemberWithName('len').GetValue()) + if data and length: + error = lldb.SBError() + return '"%s"' % var.process.ReadCStringFromMemory(int(data, 16), length + 1, error) + return '""' + + +def format_struct(var, debugger, include_type=True, indent=0, type_name=""): + children = [] + indent_str = ' ' * indent + next_indent_str = ' ' * (indent + 1) + + for i in range(var.GetNumChildren()): + child = var.GetChildAtIndex(i) + child_name = child.GetName() + child_value = format_value( + child, debugger, include_type=False, indent=indent+1) + children.append(f"{child_name} = {child_value}") + + if len(children) > 5: # 如果字段数量大于5,则进行折行显示 + struct_content = "{\n" + ",\n".join( + [f"{next_indent_str}{child}" for child in children]) + f"\n{indent_str}}}" + else: + struct_content = f"{{{', '.join(children)}}}" + + if include_type: + return f"{type_name}{struct_content}" + else: + return struct_content + + +def format_pointer(var, debugger, indent, type_name): + if not var.IsValid() or var.GetValueAsUnsigned() == 0: + return "" + return var.GetValue() # Return the address as a string + + +def map_type_name(type_name): + # Handle pointer types + if type_name.endswith('*'): + base_type = type_name[:-1].strip() + mapped_base_type = map_type_name(base_type) + return f"*{mapped_base_type}" + + # Map other types + type_mapping = { + 'long': 'int', + 'void': 'unsafe.Pointer', + 'char': 'byte', + 'short': 'int16', + 'int': 'int32', + 'long long': 'int64', + 'unsigned char': 'uint8', + 'unsigned short': 'uint16', + 'unsigned int': 'uint32', + 'unsigned long': 'uint', + 'unsigned long long': 'uint64', + 'float': 'float32', + 'double': 'float64', + } + + for c_type, go_type in type_mapping.items(): + if type_name.startswith(c_type): + return type_name.replace(c_type, go_type, 1) + + return type_name diff --git a/_lldb/runlldb.sh b/_lldb/runlldb.sh new file mode 100755 index 00000000..39da6860 --- /dev/null +++ b/_lldb/runlldb.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +# Source common functions and variables +# shellcheck source=./_lldb/common.sh +source "$(dirname "$0")/common.sh" + +executable="$1" + +# Run LLDB +"$LLDB_PATH" "$executable" diff --git a/_lldb/runtest.sh b/_lldb/runtest.sh new file mode 100755 index 00000000..c1047ac9 --- /dev/null +++ b/_lldb/runtest.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +set -e + +# Source common functions and variables +# shellcheck source=./_lldb/common.sh +source "$(dirname "$0")/common.sh" + +# Parse command-line arguments +package_path="$DEFAULT_PACKAGE_PATH" +verbose=False +interactive=False +plugin_path=None + +while [[ $# -gt 0 ]]; do + case $1 in + -v|--verbose) + verbose=True + shift + ;; + -i|--interactive) + interactive=True + shift + ;; + -p|--plugin) + plugin_path="\"$2\"" + shift 2 + ;; + *) + package_path="$1" + shift + ;; + esac +done + +# Build the project +build_project "$package_path" + +# Prepare LLDB commands +lldb_commands=( + "command script import _lldb/test.py" + "script test.run_tests(\\\"${package_path}/out\\\", [\\\"${package_path}/in.go\\\"], ${verbose}, ${interactive}, ${plugin_path})" +) + +# Add quit command if not in interactive mode +if [ "$interactive" = False ]; then + lldb_commands+=("quit") +fi + +# Run LLDB with prepared commands +lldb_command_string="" +for cmd in "${lldb_commands[@]}"; do + lldb_command_string+=" -O \"$cmd\"" +done + +eval "$LLDB_PATH $lldb_command_string" diff --git a/_lldb/test.py b/_lldb/test.py new file mode 100644 index 00000000..f7bfcab8 --- /dev/null +++ b/_lldb/test.py @@ -0,0 +1,390 @@ +# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring + +import os +import sys +import argparse +import signal +from dataclasses import dataclass, field +from typing import List, Optional, Set, Dict, Any +import lldb +import llgo_plugin # Add this import + + +class LLDBTestException(Exception): + pass + + +def log(*args: Any, **kwargs: Any) -> None: + print(*args, **kwargs, flush=True) + + +@dataclass +class Test: + source_file: str + line_number: int + variable: str + expected_value: str + + +@dataclass +class TestResult: + test: Test + status: str + actual: Optional[str] = None + message: Optional[str] = None + missing: Optional[Set[str]] = None + extra: Optional[Set[str]] = None + + +@dataclass +class TestCase: + source_file: str + start_line: int + end_line: int + tests: List[Test] + + +@dataclass +class CaseResult: + test_case: TestCase + function: str + results: List[TestResult] + + +@dataclass +class TestResults: + total: int = 0 + passed: int = 0 + failed: int = 0 + case_results: List[CaseResult] = field(default_factory=list) + + +class LLDBDebugger: + def __init__(self, executable_path: str, plugin_path: Optional[str] = None): + self.executable_path: str = executable_path + self.plugin_path: Optional[str] = plugin_path + self.debugger: lldb.SBDebugger = lldb.SBDebugger.Create() + self.debugger.SetAsync(False) + self.target: Optional[lldb.SBTarget] = None + self.process: Optional[lldb.SBProcess] = None + self.type_mapping: Dict[str, str] = { + 'long': 'int', + 'unsigned long': 'uint', + # Add more mappings as needed + } + + def setup(self) -> None: + if self.plugin_path: + self.debugger.HandleCommand( + f'command script import "{self.plugin_path}"') + self.target = self.debugger.CreateTarget(self.executable_path) + if not self.target: + raise LLDBTestException( + f"Failed to create target for {self.executable_path}") + + self.debugger.HandleCommand( + 'command script add -f llgo_plugin.print_go_expression p') + self.debugger.HandleCommand( + 'command script add -f llgo_plugin.print_all_variables v') + + def set_breakpoint(self, file_spec: str, line_number: int) -> lldb.SBBreakpoint: + bp = self.target.BreakpointCreateByLocation(file_spec, line_number) + if not bp.IsValid(): + raise LLDBTestException( + f"Failed to set breakpoint at {file_spec}: {line_number}") + return bp + + def run_to_breakpoint(self) -> None: + if not self.process: + self.process = self.target.LaunchSimple(None, None, os.getcwd()) + else: + self.process.Continue() + if self.process.GetState() != lldb.eStateStopped: + raise LLDBTestException("Process didn't stop at breakpoint") + + def get_variable_value(self, var_expression: str) -> Optional[str]: + frame = self.process.GetSelectedThread().GetFrameAtIndex(0) + + parts = var_expression.split('.') + var = frame.FindVariable(parts[0]) + + for part in parts[1:]: + if not var.IsValid(): + return None + + if '[' in part and ']' in part: + array_name, index = part.split('[') + index = int(index.rstrip(']')) + var = var.GetChildAtIndex(index) + elif var.GetType().IsPointerType(): + var = var.Dereference() + var = var.GetChildMemberWithName(part) + else: + var = var.GetChildMemberWithName(part) + + return llgo_plugin.format_value(var, self.debugger) if var.IsValid() else None + + def get_all_variable_names(self) -> Set[str]: + frame = self.process.GetSelectedThread().GetFrameAtIndex(0) + return set(var.GetName() for var in frame.GetVariables(True, True, True, False)) + + def get_current_function_name(self) -> str: + frame = self.process.GetSelectedThread().GetFrameAtIndex(0) + return frame.GetFunctionName() + + def cleanup(self) -> None: + if self.process and self.process.IsValid(): + self.process.Kill() + lldb.SBDebugger.Destroy(self.debugger) + + def run_console(self) -> bool: + log("\nEntering LLDB interactive mode.") + log("Type 'quit' to exit and continue with the next test case.") + log("Use Ctrl+D to exit and continue, or Ctrl+C to abort all tests.") + + old_stdin, old_stdout, old_stderr = sys.stdin, sys.stdout, sys.stderr + sys.stdin, sys.stdout, sys.stderr = sys.__stdin__, sys.__stdout__, sys.__stderr__ + + self.debugger.SetAsync(True) + self.debugger.HandleCommand("settings set auto-confirm true") + self.debugger.HandleCommand("command script import lldb") + + interpreter = self.debugger.GetCommandInterpreter() + continue_tests = True + + def keyboard_interrupt_handler(_sig: Any, _frame: Any) -> None: + nonlocal continue_tests + log("\nTest execution aborted by user.") + continue_tests = False + raise KeyboardInterrupt + + original_handler = signal.signal( + signal.SIGINT, keyboard_interrupt_handler) + + try: + while continue_tests: + log("\n(lldb) ", end="") + try: + command = input().strip() + except EOFError: + log("\nExiting LLDB interactive mode. Continuing with next test case.") + break + except KeyboardInterrupt: + break + + if command.lower() == 'quit': + log("\nExiting LLDB interactive mode. Continuing with next test case.") + break + + result = lldb.SBCommandReturnObject() + interpreter.HandleCommand(command, result) + log(result.GetOutput().rstrip() if result.Succeeded() + else result.GetError().rstrip()) + + finally: + signal.signal(signal.SIGINT, original_handler) + sys.stdin, sys.stdout, sys.stderr = old_stdin, old_stdout, old_stderr + + return continue_tests + + +def parse_expected_values(source_files: List[str]) -> List[TestCase]: + test_cases = [] + for source_file in source_files: + with open(source_file, 'r', encoding='utf-8') as f: + content = f.readlines() + i = 0 + while i < len(content): + line = content[i].strip() + if line.startswith('// Expected:'): + start_line = i + 1 + tests = [] + i += 1 + while i < len(content): + line = content[i].strip() + if not line.startswith('//'): + break + parts = line.lstrip('//').strip().split(':', 1) + if len(parts) == 2: + var, value = map(str.strip, parts) + tests.append(Test(source_file, i + 1, var, value)) + i += 1 + end_line = i + test_cases.append( + TestCase(source_file, start_line, end_line, tests)) + else: + i += 1 + return test_cases + + +def execute_tests(executable_path: str, test_cases: List[TestCase], verbose: bool, interactive: bool, plugin_path: Optional[str]) -> TestResults: + results = TestResults() + + for test_case in test_cases: + debugger = LLDBDebugger(executable_path, plugin_path) + try: + if verbose: + log(f"Setting breakpoint at {test_case.source_file}: {test_case.end_line}") + debugger.setup() + debugger.set_breakpoint(test_case.source_file, test_case.end_line) + debugger.run_to_breakpoint() + + all_variable_names = debugger.get_all_variable_names() + + case_result = execute_test_case( + debugger, test_case, all_variable_names) + + results.total += len(case_result.results) + results.passed += sum(1 for r in case_result.results if r.status == 'pass') + results.failed += sum(1 for r in case_result.results if r.status != 'pass') + results.case_results.append(case_result) + + case = case_result.test_case + loc = f"{case.source_file}:{case.start_line}-{case.end_line}" + if verbose or interactive or any(r.status != 'pass' for r in case_result.results): + log(f"\nTest case: {loc} in function '{case_result.function}'") + for result in case_result.results: + print_test_result(result, verbose=verbose) + + if interactive and any(r.status != 'pass' for r in case_result.results): + log("\nTest case failed. Entering LLDB interactive mode.") + continue_tests = debugger.run_console() + if not continue_tests: + log("Aborting all tests.") + break + + finally: + debugger.cleanup() + + return results + + +def run_tests(executable_path: str, source_files: List[str], verbose: bool, interactive: bool, plugin_path: Optional[str]) -> None: + test_cases = parse_expected_values(source_files) + if verbose: + log(f"Running tests for {', '.join(source_files)} with {executable_path}") + log(f"Found {len(test_cases)} test cases") + + results = execute_tests(executable_path, test_cases, + verbose, interactive, plugin_path) + print_test_results(results) + + if results.total != results.passed: + os._exit(1) + + +def execute_test_case(debugger: LLDBDebugger, test_case: TestCase, all_variable_names: Set[str]) -> CaseResult: + results = [] + + for test in test_case.tests: + if test.variable == "all variables": + result = execute_all_variables_test(test, all_variable_names) + else: + result = execute_single_variable_test(debugger, test) + results.append(result) + + return CaseResult(test_case, debugger.get_current_function_name(), results) + + +def execute_all_variables_test(test: Test, all_variable_names: Set[str]) -> TestResult: + expected_vars = set(test.expected_value.split()) + if expected_vars == all_variable_names: + return TestResult( + test=test, + status='pass', + actual=all_variable_names + ) + else: + return TestResult( + test=test, + status='fail', + actual=all_variable_names, + missing=expected_vars - all_variable_names, + extra=all_variable_names - expected_vars + ) + + +def execute_single_variable_test(debugger: LLDBDebugger, test: Test) -> TestResult: + actual_value = debugger.get_variable_value(test.variable) + if actual_value is None: + return TestResult( + test=test, + status='error', + message=f'Unable to fetch value for {test.variable}' + ) + + actual_value = actual_value.strip() + expected_value = test.expected_value.strip() + + if actual_value == expected_value: + return TestResult( + test=test, + status='pass', + actual=actual_value + ) + else: + return TestResult( + test=test, + status='fail', + actual=actual_value + ) + + +def print_test_results(results: TestResults) -> None: + log("\nTest results:") + log(f" Total tests: {results.total}") + log(f" Passed tests: {results.passed}") + log(f" Failed tests: {results.failed}") + if results.total == results.passed: + log("All tests passed!") + else: + log("Some tests failed") + + +def print_test_result(result: TestResult, verbose: bool) -> None: + status_symbol = "✓" if result.status == 'pass' else "✗" + status_text = "Pass" if result.status == 'pass' else "Fail" + test = result.test + + if result.status == 'pass': + if verbose: + log(f"{status_symbol} Line {test.line_number}, {test.variable}: {status_text}") + if test.variable == 'all variables': + log(f" Variables: {', '.join(sorted(result.actual))}") + else: # fail or error + log(f"{status_symbol} Line {test.line_number}, {test.variable}: {status_text}") + if test.variable == 'all variables': + if result.missing: + log(f" Missing variables: {', '.join(sorted(result.missing))}") + if result.extra: + log(f" Extra variables: {', '.join(sorted(result.extra))}") + log(f" Expected: {', '.join(sorted(test.expected_value.split()))}") + log(f" Actual: {', '.join(sorted(result.actual))}") + elif result.status == 'error': + log(f" Error: {result.message}") + else: + log(f" Expected: {test.expected_value}") + log(f" Actual: {result.actual}") + + +def main() -> None: + log(sys.argv) + parser = argparse.ArgumentParser( + description="LLDB 18 Debug Script with DWARF 5 Support") + parser.add_argument("executable", help="Path to the executable") + parser.add_argument("sources", nargs='+', help="Paths to the source files") + parser.add_argument("-v", "--verbose", action="store_true", + help="Enable verbose output") + parser.add_argument("-i", "--interactive", action="store_true", + help="Enable interactive mode on test failure") + parser.add_argument("--plugin", help="Path to the LLDB plugin") + args = parser.parse_args() + + plugin_path = args.plugin or os.path.join(os.path.dirname( + os.path.realpath(__file__)), "go_lldb_plugin.py") + run_tests(args.executable, args.sources, + args.verbose, args.interactive, plugin_path) + + +if __name__ == "__main__": + main() diff --git a/chore/gentests/gentests.go b/chore/gentests/gentests.go index 576a2aee..ea465748 100644 --- a/chore/gentests/gentests.go +++ b/chore/gentests/gentests.go @@ -21,6 +21,7 @@ import ( "os" "strings" + "github.com/goplus/llgo/cl" "github.com/goplus/llgo/internal/llgen" "github.com/goplus/llgo/ssa" "github.com/goplus/mod" @@ -41,6 +42,20 @@ func main() { llgenDir(dir+"/cl/_testdata", "") } +func isDbgSymEnabled(flagsFile string) bool { + data, err := os.ReadFile(flagsFile) + if err != nil { + return false + } + toks := strings.Split(strings.Join(strings.Split(string(data), "\n"), " "), " ") + for _, tok := range toks { + if tok == "-dbg" { + return true + } + } + return false +} + func llgenDir(dir string, pkgPath ...string) { fis, err := os.ReadDir(dir) check(err) @@ -51,7 +66,10 @@ func llgenDir(dir string, pkgPath ...string) { } testDir := dir + "/" + name fmt.Fprintln(os.Stderr, "llgen", testDir) - os.Chdir(testDir) + check(os.Chdir(testDir)) + dbg := isDbgSymEnabled("flags.txt") + cl.EnableDebugSymbols(dbg) + llgen.SmartDoFile("in.go", pkgPath...) } } diff --git a/chore/llgen/llgen.go b/chore/llgen/llgen.go index ec27e29b..d93e8d9d 100644 --- a/chore/llgen/llgen.go +++ b/chore/llgen/llgen.go @@ -20,15 +20,16 @@ import ( "fmt" "os" + "github.com/goplus/llgo/internal/build" "github.com/goplus/llgo/internal/llgen" ) func main() { if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "Usage: llgen [pkgPath]") + fmt.Fprintln(os.Stderr, "Usage: llgen [flags] [pkgPath]") return } - - llgen.Init() - llgen.SmartDoFile(os.Args[1], os.Args[2:]...) + llgen.Init(build.IsDebugEnabled()) + args := os.Args[1:] + llgen.SmartDoFile(args[0], args[1:]...) } diff --git a/cl/_testdata/debug/flags.txt b/cl/_testdata/debug/flags.txt new file mode 100644 index 00000000..ec2d3db9 --- /dev/null +++ b/cl/_testdata/debug/flags.txt @@ -0,0 +1 @@ +-dbg \ No newline at end of file diff --git a/cl/_testdata/debug/in.go b/cl/_testdata/debug/in.go new file mode 100644 index 00000000..bba503c6 --- /dev/null +++ b/cl/_testdata/debug/in.go @@ -0,0 +1,323 @@ +package main + +import "errors" + +type Base struct { + name string +} + +type E struct { + // Base + i int +} +type StructWithAllTypeFields struct { + i8 int8 + i16 int16 + i32 int32 + i64 int64 + i int + u8 uint8 + u16 uint16 + u32 uint32 + u64 uint64 + u uint + f32 float32 + f64 float64 + b bool + c64 complex64 + c128 complex128 + slice []int + arr [3]int + arr2 [3]E + s string + e E + pf *StructWithAllTypeFields // resursive + pi *int + intr Interface + m map[string]uint64 + c chan int + err error + fn func(string) (int, error) + pad1 int + pad2 int +} + +type Interface interface { + Foo(a []int, b string) int +} + +type Struct struct{} + +func (s *Struct) Foo(a []int, b string) int { + return 1 +} + +func FuncWithAllTypeStructParam(s StructWithAllTypeFields) { + println(&s) + // Expected: + // all variables: s + // s.i8: '\x01' + // s.i16: 2 + // s.i32: 3 + // s.i64: 4 + // s.i: 5 + // s.u8: '\x06' + // s.u16: 7 + // s.u32: 8 + // s.u64: 9 + // s.u: 10 + // s.f32: 11 + // s.f64: 12 + // s.b: true + // s.c64: complex64{real = 13, imag = 14} + // s.c128: complex128{real = 15, imag = 16} + // s.slice: []int{21, 22, 23} + // s.arr: [3]int{24, 25, 26} + // s.arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}} + // s.s: "hello" + // s.e: github.com/goplus/llgo/cl/_testdata/debug.E{i = 30} + // s.pad1: 100 + // s.pad2: 200 + s.i8 = 8 + // Expected(skio): + // s.i8: '\x08' + println(len(s.s), s.i8) +} + +// Params is a function with all types of parameters. +func FuncWithAllTypeParams( + i8 int8, + i16 int16, + i32 int32, + i64 int64, + i int, + u8 uint8, + u16 uint16, + u32 uint32, + u64 uint64, + u uint, + f32 float32, + f64 float64, + b bool, + c64 complex64, + c128 complex128, + slice []int, + arr [3]int, + arr2 [3]E, + s string, + e E, + f StructWithAllTypeFields, + pf *StructWithAllTypeFields, + pi *int, + intr Interface, + m map[string]uint64, + c chan int, + err error, + fn func(string) (int, error), +) (int, error) { + println( + i8, i16, i32, i64, i, u8, u16, u32, u64, u, + f32, f64, b, + c64, c128, + slice, arr[0:], + s, + &e, + &f, pf, pi, intr, m, + c, + err, + fn, + ) + // Expected: + // all variables: i8 i16 i32 i64 i u8 u16 u32 u64 u f32 f64 b c64 c128 slice arr arr2 s e f pf pi intr m c err fn + // i8: '\x01' + // i16: 2 + // i32: 3 + // i64: 4 + // i: 5 + // u8: '\x06' + // u16: 7 + // u32: 8 + // u64: 9 + // u: 10 + // f32: 11 + // f64: 12 + // b: true + // c64: complex64{real = 13, imag = 14} + // c128: complex128{real = 15, imag = 16} + // slice: []int{21, 22, 23} + // arr: [3]int{24, 25, 26} + // arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 27}, {i = 28}, {i = 29}} + // s: "hello" + // e: github.com/goplus/llgo/cl/_testdata/debug.E{i = 30} + i8 = 9 + // Expected(skip): + // i8: '\x09' + println(i8) + return 1, errors.New("some error") +} + +type TinyStruct struct { + I int +} + +type SmallStruct struct { + I int + J int +} + +type MidStruct struct { + I int + J int + K int +} + +type BigStruct struct { + I int + J int + K int + L int + M int + N int + O int + P int + Q int + R int +} + +func FuncStructParams(t TinyStruct, s SmallStruct, m MidStruct, b BigStruct) { + println(&t, &s, &m, &b) + // Expected: + // all variables: t s m b + // t.I: 1 + // s.I: 2 + // s.J: 3 + // m.I: 4 + // m.J: 5 + // m.K: 6 + // b.I: 7 + // b.J: 8 + // b.K: 9 + // b.L: 10 + // b.M: 11 + // b.N: 12 + // b.O: 13 + // b.P: 14 + // b.Q: 15 + // b.R: 16 + t.I = 10 + // Expected(skip): + // all variables: t s m b + // t.I: 10 + println("done") +} + +func FuncStructPtrParams(t *TinyStruct, s *SmallStruct, m *MidStruct, b *BigStruct) { + println(t, s, m, b) + // Expected: + // all variables: t s m b + // t.I: 1 + // s.I: 2 + // s.J: 3 + // m.I: 4 + // m.J: 5 + // m.K: 6 + // b.I: 7 + // b.J: 8 + // b.K: 9 + // b.L: 10 + // b.M: 11 + // b.N: 12 + // b.O: 13 + // b.P: 14 + // b.Q: 15 + // b.R: 16 + t.I = 10 + // Expected: + // all variables: t s m b + // t.I: 10 + println("done") +} + +func main() { + FuncStructParams(TinyStruct{I: 1}, SmallStruct{I: 2, J: 3}, MidStruct{I: 4, J: 5, K: 6}, BigStruct{I: 7, J: 8, K: 9, L: 10, M: 11, N: 12, O: 13, P: 14, Q: 15, R: 16}) + FuncStructPtrParams(&TinyStruct{I: 1}, &SmallStruct{I: 2, J: 3}, &MidStruct{I: 4, J: 5, K: 6}, &BigStruct{I: 7, J: 8, K: 9, L: 10, M: 11, N: 12, O: 13, P: 14, Q: 15, R: 16}) + i := 100 + s := StructWithAllTypeFields{ + i8: 1, + i16: 2, + i32: 3, + i64: 4, + i: 5, + u8: 6, + u16: 7, + u32: 8, + u64: 9, + u: 10, + f32: 11, + f64: 12, + b: true, + c64: 13 + 14i, + c128: 15 + 16i, + slice: []int{21, 22, 23}, + arr: [3]int{24, 25, 26}, + arr2: [3]E{{i: 27}, {i: 28}, {i: 29}}, + s: "hello", + e: E{i: 30}, + pf: &StructWithAllTypeFields{}, + pi: &i, + intr: &Struct{}, + m: map[string]uint64{"a": 31, "b": 32}, + c: make(chan int), + err: errors.New("Test error"), + fn: func(s string) (int, error) { + println("fn:", s) + i = 201 + return 1, errors.New("fn error") + }, + pad1: 100, + pad2: 200, + } + globalStructPtr = &s + globalStruct = s + println("globalInt:", globalInt) + println("s:", &s) + FuncWithAllTypeStructParam(s) + println("called function with struct") + i, err := FuncWithAllTypeParams( + s.i8, s.i16, s.i32, s.i64, s.i, s.u8, s.u16, s.u32, s.u64, s.u, + s.f32, s.f64, s.b, + s.c64, s.c128, + s.slice, s.arr, s.arr2, + s.s, + s.e, s, + s.pf, s.pi, + s.intr, + s.m, + s.c, + s.err, + s.fn, + ) + println(i, err) + println("called function with types") + println(globalStructPtr) + println(&globalStruct) + // Expected(skip): + // all variables: globalInt globalStruct globalStructPtr s i err + // s.i8: '\x01' + // s.i16: 2 + s.i8 = 0x12 + println(s.i8) + // Expected(skip): + // all variables: globalInt globalStruct globalStructPtr s i err + // s.i8: '\x12' + // globalStruct.i8: '\x01' + println((*globalStructPtr).i8) + println("done") + println("") + println(&s, &globalStruct, globalStructPtr.i16, globalStructPtr) + globalStructPtr = nil +} + +var globalInt int = 301 +var globalStruct StructWithAllTypeFields +var globalStructPtr *StructWithAllTypeFields diff --git a/cl/_testdata/debug/out.ll b/cl/_testdata/debug/out.ll new file mode 100644 index 00000000..1c8a0e79 --- /dev/null +++ b/cl/_testdata/debug/out.ll @@ -0,0 +1 @@ +; \ No newline at end of file diff --git a/cl/_testgo/selects/out.ll b/cl/_testgo/selects/out.ll index ee7881bc..196b8fbb 100644 --- a/cl/_testgo/selects/out.ll +++ b/cl/_testgo/selects/out.ll @@ -149,48 +149,50 @@ _llgo_1: ; preds = %_llgo_4, %_llgo_2 ret i32 0 _llgo_2: ; preds = %_llgo_0 - %70 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %71 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %70, i32 0, i32 0 - store ptr @1, ptr %71, align 8 - %72 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %70, i32 0, i32 1 - store i64 4, ptr %72, align 4 - %73 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %70, align 8 - call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %73) + %70 = extractvalue { i64, i1, {}, {} } %67, 2 + %71 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %72 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %71, i32 0, i32 0 + store ptr @1, ptr %72, align 8 + %73 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %71, i32 0, i32 1 + store i64 4, ptr %73, align 4 + %74 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %71, align 8 + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %74) call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) br label %_llgo_1 _llgo_3: ; preds = %_llgo_0 - %74 = icmp eq i64 %68, 1 - br i1 %74, label %_llgo_4, label %_llgo_5 + %75 = icmp eq i64 %68, 1 + br i1 %75, label %_llgo_4, label %_llgo_5 _llgo_4: ; preds = %_llgo_3 - %75 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %76 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %75, i32 0, i32 0 - store ptr @2, ptr %76, align 8 - %77 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %75, i32 0, i32 1 - store i64 4, ptr %77, align 4 - %78 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %75, align 8 - call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %78) + %76 = extractvalue { i64, i1, {}, {} } %67, 3 + %77 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %78 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %77, i32 0, i32 0 + store ptr @2, ptr %78, align 8 + %79 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %77, i32 0, i32 1 + store i64 4, ptr %79, align 4 + %80 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %77, align 8 + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %80) call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) br label %_llgo_1 _llgo_5: ; preds = %_llgo_3 - %79 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %80 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %79, i32 0, i32 0 - store ptr @3, ptr %80, align 8 - %81 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %79, i32 0, i32 1 - store i64 31, ptr %81, align 4 - %82 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %79, align 8 - %83 = load ptr, ptr @_llgo_string, align 8 - %84 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) - store %"github.com/goplus/llgo/internal/runtime.String" %82, ptr %84, align 8 - %85 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 - %86 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %85, i32 0, i32 0 - store ptr %83, ptr %86, align 8 - %87 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %85, i32 0, i32 1 - store ptr %84, ptr %87, align 8 - %88 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %85, align 8 - call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %88) + %81 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %82 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %81, i32 0, i32 0 + store ptr @3, ptr %82, align 8 + %83 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %81, i32 0, i32 1 + store i64 31, ptr %83, align 4 + %84 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %81, align 8 + %85 = load ptr, ptr @_llgo_string, align 8 + %86 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/internal/runtime.String" %84, ptr %86, align 8 + %87 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %88 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %87, i32 0, i32 0 + store ptr %85, ptr %88, align 8 + %89 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %87, i32 0, i32 1 + store ptr %86, ptr %89, align 8 + %90 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %87, align 8 + call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %90) unreachable } @@ -289,33 +291,34 @@ _llgo_3: ; preds = %_llgo_0 br i1 %53, label %_llgo_4, label %_llgo_5 _llgo_4: ; preds = %_llgo_3 - %54 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %55 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %54, i32 0, i32 0 - store ptr @6, ptr %55, align 8 - %56 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %54, i32 0, i32 1 - store i64 4, ptr %56, align 4 - %57 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %54, align 8 - call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %57) + %54 = extractvalue { i64, i1, {} } %46, 2 + %55 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %56 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %55, i32 0, i32 0 + store ptr @6, ptr %56, align 8 + %57 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %55, i32 0, i32 1 + store i64 4, ptr %57, align 4 + %58 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %55, align 8 + call void @"github.com/goplus/llgo/internal/runtime.PrintString"(%"github.com/goplus/llgo/internal/runtime.String" %58) call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) br label %_llgo_1 _llgo_5: ; preds = %_llgo_3 - %58 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 - %59 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %58, i32 0, i32 0 - store ptr @3, ptr %59, align 8 - %60 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %58, i32 0, i32 1 - store i64 31, ptr %60, align 4 - %61 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %58, align 8 - %62 = load ptr, ptr @_llgo_string, align 8 - %63 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) - store %"github.com/goplus/llgo/internal/runtime.String" %61, ptr %63, align 8 - %64 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 - %65 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %64, i32 0, i32 0 - store ptr %62, ptr %65, align 8 - %66 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %64, i32 0, i32 1 + %59 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8 + %60 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %59, i32 0, i32 0 + store ptr @3, ptr %60, align 8 + %61 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %59, i32 0, i32 1 + store i64 31, ptr %61, align 4 + %62 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %59, align 8 + %63 = load ptr, ptr @_llgo_string, align 8 + %64 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocU"(i64 16) + store %"github.com/goplus/llgo/internal/runtime.String" %62, ptr %64, align 8 + %65 = alloca %"github.com/goplus/llgo/internal/runtime.eface", align 8 + %66 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %65, i32 0, i32 0 store ptr %63, ptr %66, align 8 - %67 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %64, align 8 - call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %67) + %67 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.eface", ptr %65, i32 0, i32 1 + store ptr %64, ptr %67, align 8 + %68 = load %"github.com/goplus/llgo/internal/runtime.eface", ptr %65, align 8 + call void @"github.com/goplus/llgo/internal/runtime.Panic"(%"github.com/goplus/llgo/internal/runtime.eface" %68) unreachable } diff --git a/cl/blocks/block_test.go b/cl/blocks/block_test.go index 92b00399..f30b582c 100644 --- a/cl/blocks/block_test.go +++ b/cl/blocks/block_test.go @@ -104,7 +104,7 @@ func testBlockInfo(t *testing.T, src any, fname, expected, fn string) { pkg := types.NewPackage(name, name) imp := packages.NewImporter(fset) foo, _, err := ssautil.BuildPackage( - &types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions|ssa.InstantiateGenerics) + &types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions|ssa.InstantiateGenerics|ssa.GlobalDebug) if err != nil { t.Fatal("BuildPackage failed:", err) } diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go index 5d4c2913..bd0b0178 100644 --- a/cl/cltest/cltest.go +++ b/cl/cltest/cltest.go @@ -107,6 +107,20 @@ func Pkg(t *testing.T, pkgPath, outFile string) { } } +func isDbgSymEnabled(flagsFile string) bool { + data, err := os.ReadFile(flagsFile) + if err != nil { + return false + } + toks := strings.Split(strings.Join(strings.Split(string(data), "\n"), " "), " ") + for _, tok := range toks { + if tok == "-dbg" { + return true + } + } + return false +} + func testFrom(t *testing.T, pkgDir, sel string, byLLGen bool) { if sel != "" && !strings.Contains(pkgDir, sel) { return @@ -114,6 +128,12 @@ func testFrom(t *testing.T, pkgDir, sel string, byLLGen bool) { log.Println("Parsing", pkgDir) in := pkgDir + "/in.go" out := pkgDir + "/out.ll" + dbg := isDbgSymEnabled(pkgDir + "/flags.txt") + if dbg { + cl.EnableDebugSymbols(true) + defer cl.EnableDebugSymbols(false) + cl.DebugSymbols() // just for coverage + } b, err := os.ReadFile(out) if err != nil { t.Fatal("ReadFile failed:", err) @@ -140,7 +160,7 @@ func TestCompileEx(t *testing.T, src any, fname, expected string) { pkg := types.NewPackage(name, name) imp := packages.NewImporter(fset) foo, _, err := ssautil.BuildPackage( - &types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions|ssa.InstantiateGenerics) + &types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions|ssa.InstantiateGenerics|ssa.GlobalDebug) if err != nil { t.Fatal("BuildPackage failed:", err) } diff --git a/cl/compile.go b/cl/compile.go index 97a991fd..cb697c7e 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -46,8 +46,9 @@ const ( ) var ( - debugInstr bool - debugGoSSA bool + debugInstr bool + debugGoSSA bool + debugSymbols bool ) // SetDebug sets debug flags. @@ -56,6 +57,15 @@ func SetDebug(dbgFlags dbgFlags) { debugGoSSA = (dbgFlags & DbgFlagGoSSA) != 0 } +// EnableDebugSymbols enables debug symbols. +func EnableDebugSymbols(b bool) { + debugSymbols = b +} + +func DebugSymbols() bool { + return debugSymbols +} + // ----------------------------------------------------------------------------- type instrOrValue interface { @@ -249,6 +259,10 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun log.Println("==> FuncBody", name) } b := fn.NewBuilder() + if debugSymbols { + b.DebugFunction(fn, p.goProg.Fset.Position(f.Pos())) + b.DISetCurrentDebugLocation(p.fn, p.goProg.Fset.Position(f.Pos())) + } p.bvals = make(map[ssa.Value]llssa.Expr) off := make([]int, len(f.Blocks)) for i, block := range f.Blocks { @@ -277,6 +291,17 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun return fn, nil, goFunc } +func (p *context) debugParams(b llssa.Builder, f *ssa.Function) { + for i, param := range f.Params { + pos := p.goProg.Fset.Position(param.Pos()) + v := p.compileValue(b, param) + ty := param.Type() + argNo := i + 1 + div := b.DIVarParam(p.fn, pos, param.Name(), p.prog.Type(ty, llssa.InGo), argNo) + b.DIDeclare(v, div, p.fn, pos, p.fn.Block(0)) + } +} + func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, doMainInit, doModInit bool) llssa.BasicBlock { var last int var pyModInit bool @@ -286,6 +311,10 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do var instrs = block.Instrs[n:] var ret = fn.Block(block.Index) b.SetBlock(ret) + // place here to avoid wrong current-block + if debugSymbols && block.Index == 0 { + p.debugParams(b, block.Parent()) + } if doModInit { if pyModInit = p.pyMod != ""; pyModInit { last = len(instrs) - 1 @@ -454,6 +483,11 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue } log.Panicln("unreachable:", iv) } + if debugSymbols { + if v, ok := iv.(ssa.Instruction); ok { + b.DISetCurrentDebugLocation(p.fn, p.goProg.Fset.Position(v.Pos())) + } + } switch v := iv.(type) { case *ssa.Call: ret = p.call(b, llssa.Call, &v.Call) @@ -684,11 +718,46 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { ch := p.compileValue(b, v.Chan) x := p.compileValue(b, v.X) b.Send(ch, x) + case *ssa.DebugRef: + if debugSymbols { + object := v.Object() + variable, ok := object.(*types.Var) + if !ok { + // Not a local variable. + return + } + if variable.IsField() { + // skip *ssa.FieldAddr + return + } + pos := p.goProg.Fset.Position(v.Pos()) + value := p.compileValue(b, v.X) + fn := v.Parent() + dbgVar := p.getLocalVariable(b, fn, variable) + if v.IsAddr { + // *ssa.Alloc + b.DIDeclare(value, dbgVar, p.fn, pos, b.Func.Block(v.Block().Index)) + } else { + b.DIValue(value, dbgVar, p.fn, pos, b.Func.Block(v.Block().Index)) + } + } default: panic(fmt.Sprintf("compileInstr: unknown instr - %T\n", instr)) } } +func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.Var) llssa.DIVar { + pos := p.fset.Position(v.Pos()) + t := b.Prog.Type(v.Type(), llssa.InGo) + for i, param := range fn.Params { + if param.Object().(*types.Var) == v { + argNo := i + 1 + return b.DIVarParam(p.fn, pos, v.Name(), t, argNo) + } + } + return b.DIVarAuto(p.fn, pos, v.Name(), t) +} + func (p *context) compileFunction(v *ssa.Function) (goFn llssa.Function, pyFn llssa.PyObjRef, kind int) { // TODO(xsw) v.Pkg == nil: means auto generated function? if v.Pkg == p.goPkg || v.Pkg == nil { @@ -710,7 +779,7 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr { fn := v.Parent() for idx, param := range fn.Params { if param == v { - return p.fn.Param(idx) + return b.Param(idx) } } case *ssa.Function: @@ -720,7 +789,12 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr { } return pyFn.Expr case *ssa.Global: - return p.varOf(b, v) + val := p.varOf(b, v) + if debugSymbols { + pos := p.fset.Position(v.Pos()) + b.DIGlobal(val, v.Name(), pos) + } + return val case *ssa.Const: t := types.Default(v.Type()) bg := llssa.InGo @@ -798,6 +872,9 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [ prog.SetRuntime(pkgTypes) } ret = prog.NewPackage(pkgName, pkgPath) + if debugSymbols { + ret.InitDebugSymbols(pkgName, pkgPath, pkgProg.Fset) + } ctx := &context{ prog: prog, diff --git a/internal/build/build.go b/internal/build/build.go index 6bffc12f..7e970a78 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -121,6 +121,7 @@ const ( func Do(args []string, conf *Config) { flags, patterns, verbose := ParseArgs(args, buildFlags) + cl.EnableDebugSymbols(IsDebugEnabled()) flags = append(flags, "-tags", "llgo") cfg := &packages.Config{ Mode: loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile, @@ -236,7 +237,7 @@ func isNeedRuntimeOrPyInit(pkg *packages.Package) (needRuntime, needPyInit bool) } const ( - ssaBuildMode = ssa.SanityCheckFunctions | ssa.InstantiateGenerics + ssaBuildMode = ssa.SanityCheckFunctions | ssa.InstantiateGenerics | ssa.GlobalDebug ) type context struct { @@ -436,6 +437,9 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, llFiles } } args = append(args, exargs...) + if cl.DebugSymbols() { + args = append(args, "-gdwarf-5") + } // TODO(xsw): show work if verbose { @@ -604,6 +608,13 @@ var ( } ) +const llgoDebug = "LLGO_DEBUG" + +func IsDebugEnabled() bool { + llgoDbgVal := strings.ToLower(os.Getenv(llgoDebug)) + return llgoDbgVal == "1" || llgoDbgVal == "true" || llgoDbgVal == "on" +} + func ParseArgs(args []string, swflags map[string]bool) (flags, patterns []string, verbose bool) { n := len(args) for i := 0; i < n; i++ { @@ -611,11 +622,11 @@ func ParseArgs(args []string, swflags map[string]bool) (flags, patterns []string if strings.HasPrefix(arg, "-") { checkFlag(arg, &i, &verbose, swflags) } else { - flags, patterns = args[:i], args[i:] + patterns = args[i:] + flags = args[:i] return } } - flags = args return } diff --git a/internal/llgen/llgen.go b/internal/llgen/llgen.go index 9dfe75f5..9b6de2e6 100644 --- a/internal/llgen/llgen.go +++ b/internal/llgen/llgen.go @@ -25,10 +25,11 @@ import ( llssa "github.com/goplus/llgo/ssa" ) -func Init() { +func Init(enableDbg bool) { llssa.Initialize(llssa.InitAll) llssa.SetDebug(llssa.DbgFlagAll) cl.SetDebug(cl.DbgFlagAll) + cl.EnableDebugSymbols(enableDbg) } func PkgPath(dir string) string { diff --git a/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index 85a62b25..a55bbfb5 100644 --- a/internal/llgen/llgenf.go +++ b/internal/llgen/llgenf.go @@ -90,7 +90,7 @@ func genFrom(fileOrPkg string, pkgPath string) string { initial, err := packages.LoadEx(dedup, prog.TypeSizes, cfg, fileOrPkg) check(err) - _, pkgs := ssautil.AllPackages(initial, ssa.SanityCheckFunctions|ssa.InstantiateGenerics) + _, pkgs := ssautil.AllPackages(initial, ssa.SanityCheckFunctions|ssa.InstantiateGenerics|ssa.GlobalDebug) pkg := initial[0] ssaPkg := pkgs[0] diff --git a/ssa/decl.go b/ssa/decl.go index 96751a37..18b194b6 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -17,6 +17,7 @@ package ssa import ( + "go/token" "go/types" "log" "strconv" @@ -173,6 +174,8 @@ type aFunction struct { freeVars Expr base int // base = 1 if hasFreeVars; base = 0 otherwise hasVArg bool + + diFunc DIFunction } // Function represents a function or method. @@ -271,7 +274,7 @@ func (p Function) NewBuilder() Builder { b := prog.ctx.NewBuilder() // TODO(xsw): Finalize may cause panic, so comment it. // b.Finalize() - return &aBuilder{b, nil, p, p.Pkg, prog} + return &aBuilder{b, nil, p, p.Pkg, prog, make(map[Expr]dbgExpr)} } // HasBody reports whether the function has a body. @@ -324,4 +327,33 @@ func (p Function) SetRecover(blk BasicBlock) { p.recov = blk } +func (p Function) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta { + if p.diFunc == nil { + paramTypes := make([]llvm.Metadata, len(p.params)) + for i, t := range p.params { + paramTypes[i] = b.diType(t, pos).ll + } + diFuncType := b.di.CreateSubroutineType(llvm.DISubroutineType{ + File: b.file(pos.Filename).ll, + Parameters: paramTypes, + }) + p.diFunc = &aDIFunction{ + b.di.CreateFunction( + b.file(pos.Filename).ll, + llvm.DIFunction{ + Type: diFuncType, + Name: p.Name(), + LinkageName: p.Name(), + File: b.file(pos.Filename).ll, + Line: pos.Line, + IsDefinition: true, + Optimized: false, + }, + ), + } + p.impl.SetSubprogram(p.diFunc.ll) + } + return &aDIScopeMeta{p.diFunc.ll} +} + // ----------------------------------------------------------------------------- diff --git a/ssa/di.go b/ssa/di.go new file mode 100644 index 00000000..ccb0dd54 --- /dev/null +++ b/ssa/di.go @@ -0,0 +1,623 @@ +package ssa + +import ( + "debug/dwarf" + "fmt" + "go/token" + "go/types" + "path/filepath" + + "github.com/goplus/llvm" +) + +type Positioner interface { + Position(pos token.Pos) token.Position +} + +type aDIBuilder struct { + di *llvm.DIBuilder + prog Program + types map[Type]DIType + positioner Positioner +} + +type diBuilder = *aDIBuilder + +func newDIBuilder(prog Program, pkg Package, positioner Positioner) diBuilder { + m := pkg.mod + ctx := m.Context() + m.AddNamedMetadataOperand("llvm.module.flags", + ctx.MDNode([]llvm.Metadata{ + llvm.ConstInt(ctx.Int32Type(), 2, false).ConstantAsMetadata(), // Warning on mismatch + ctx.MDString("Debug Info Version"), + llvm.ConstInt(ctx.Int32Type(), 3, false).ConstantAsMetadata(), + }), + ) + m.AddNamedMetadataOperand("llvm.module.flags", + ctx.MDNode([]llvm.Metadata{ + llvm.ConstInt(ctx.Int32Type(), 7, false).ConstantAsMetadata(), // Max on mismatch + ctx.MDString("Dwarf Version"), + llvm.ConstInt(ctx.Int32Type(), 5, false).ConstantAsMetadata(), + }), + ) + + // Add llvm.ident metadata + identNode := ctx.MDNode([]llvm.Metadata{ + ctx.MDString("LLGo Compiler"), + }) + m.AddNamedMetadataOperand("llvm.ident", identNode) + + return &aDIBuilder{ + di: llvm.NewDIBuilder(m), + prog: prog, + types: make(map[*aType]DIType), + positioner: positioner, + } +} + +// ---------------------------------------------------------------------------- + +type aCompilationUnit struct { + ll llvm.Metadata +} + +type CompilationUnit = *aCompilationUnit + +func (c CompilationUnit) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta { + return &aDIScopeMeta{c.ll} +} + +var DWARF_LANG_C llvm.DwarfLang = 0x2 +var DWARF_LANG_GO llvm.DwarfLang = 0x16 + +func (b diBuilder) createCompileUnit(filename, dir string) CompilationUnit { + return &aCompilationUnit{ll: b.di.CreateCompileUnit(llvm.DICompileUnit{ + // TODO(lijie): use C language for now, change after Go plugin of LLDB is ready + Language: DWARF_LANG_C - 1, + File: filename, + Dir: dir, + Producer: "LLGo", + Optimized: false, + RuntimeVersion: 1, + })} +} + +// ---------------------------------------------------------------------------- + +type aDIScopeMeta struct { + ll llvm.Metadata +} + +type DIScopeMeta = *aDIScopeMeta + +type DIScope interface { + scopeMeta(b diBuilder, pos token.Position) DIScopeMeta +} + +// ---------------------------------------------------------------------------- + +type aDIFile struct { + ll llvm.Metadata +} + +type DIFile = *aDIFile + +func (b diBuilder) createFile(filename string) DIFile { + dir, file := filepath.Split(filename) + return &aDIFile{ll: b.di.CreateFile(file, dir)} +} + +func (f DIFile) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta { + return &aDIScopeMeta{b.file(pos.Filename).ll} +} + +// ---------------------------------------------------------------------------- + +type aDIType struct { + ll llvm.Metadata +} + +type DIType = *aDIType + +func (b diBuilder) createType(name string, ty Type, pos token.Position) DIType { + var typ llvm.Metadata + switch t := ty.RawType().(type) { + case *types.Basic: + if t.Kind() == types.UnsafePointer { + typ = b.di.CreatePointerType(llvm.DIPointerType{ + Name: name, + SizeInBits: b.prog.SizeOf(b.prog.rawType(t)) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(t) * 8), + AddressSpace: 0, + }) + return &aDIType{typ} + } + + var encoding llvm.DwarfTypeEncoding + if t.Info()&types.IsBoolean != 0 { + encoding = llvm.DW_ATE_boolean + } else if t.Info()&types.IsUnsigned != 0 { + encoding = llvm.DW_ATE_unsigned + } else if t.Info()&types.IsInteger != 0 { + encoding = llvm.DW_ATE_signed + } else if t.Info()&types.IsFloat != 0 { + encoding = llvm.DW_ATE_float + } else if t.Info()&types.IsComplex != 0 { + return b.createComplexType(ty) + } else if t.Info()&types.IsString != 0 { + return b.createStringType() + } else { + panic(fmt.Errorf("can't create debug info of basic type: %v, %T", ty.RawType(), ty.RawType())) + } + + typ = b.di.CreateBasicType(llvm.DIBasicType{ + Name: name, + SizeInBits: b.prog.SizeOf(b.prog.rawType(t)) * 8, + Encoding: encoding, + }) + case *types.Pointer: + return b.createPointerType(name, b.prog.rawType(t.Elem()), pos) + case *types.Named: + ty = b.prog.rawType(t.Underlying()) + pos = b.positioner.Position(t.Obj().Pos()) + return b.diTypeEx(name, ty, pos) + case *types.Interface: + ty := b.prog.rtType("Iface") + return b.createInterfaceType(name, ty) + case *types.Slice: + ty := b.prog.rtType("Slice") + tyElem := b.prog.rawType(t.Elem()) + return b.createSliceType(name, ty, tyElem) + case *types.Struct: + return b.createStructType(name, ty, pos) + case *types.Signature: + tyFn := b.prog.Closure(ty) + return b.createFuncPtrType(name, tyFn, pos) + case *types.Array: + return b.createArrayType(ty, t.Len()) + case *types.Chan: + return b.createChanType(name, ty, pos) + case *types.Map: + ty := b.prog.rtType("Map") + return b.createMapType(name, ty, pos) + default: + panic(fmt.Errorf("can't create debug info of type: %v, %T", ty.RawType(), ty.RawType())) + } + return &aDIType{typ} +} + +// ---------------------------------------------------------------------------- + +type aDIFunction struct { + ll llvm.Metadata +} + +type DIFunction = *aDIFunction + +// ---------------------------------------------------------------------------- + +type aDIGlobalVariableExpression struct { + ll llvm.Metadata +} + +type DIGlobalVariableExpression = *aDIGlobalVariableExpression + +func (b diBuilder) createGlobalVariableExpression(scope DIScope, pos token.Position, name, linkageName string, ty Type, isLocalToUnit bool) DIGlobalVariableExpression { + return &aDIGlobalVariableExpression{ + ll: b.di.CreateGlobalVariableExpression( + scope.scopeMeta(b, pos).ll, + llvm.DIGlobalVariableExpression{ + Name: name, + LinkageName: linkageName, + File: b.file(pos.Filename).ll, + Line: pos.Line, + Type: b.diType(ty, pos).ll, + LocalToUnit: isLocalToUnit, + AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8), + }, + ), + } +} + +// ---------------------------------------------------------------------------- + +type aDIVar struct { + ll llvm.Metadata +} + +type DIVar = *aDIVar + +func (b diBuilder) createParameterVariable(f Function, pos token.Position, name string, argNo int, ty DIType) DIVar { + return &aDIVar{ + ll: b.di.CreateParameterVariable( + f.scopeMeta(b, pos).ll, + llvm.DIParameterVariable{ + Name: name, + File: b.file(pos.Filename).ll, + Line: pos.Line, + ArgNo: argNo, + Type: ty.ll, + AlwaysPreserve: true, + }, + ), + } +} + +func (b diBuilder) createAutoVariable(scope DIScope, pos token.Position, name string, ty DIType) DIVar { + return &aDIVar{ + ll: b.di.CreateAutoVariable( + scope.scopeMeta(b, pos).ll, + llvm.DIAutoVariable{ + Name: name, + File: b.file(pos.Filename).ll, + Line: pos.Line, + Type: ty.ll, + AlwaysPreserve: true, + }, + ), + } +} + +func (b diBuilder) createStringType() DIType { + ty := b.prog.rtType("String") + return b.doCreateStructType("string", ty, token.Position{}, func(ditStruct DIType) []llvm.Metadata { + return []llvm.Metadata{ + b.createMemberType("data", ty, b.prog.CStr(), 0), + b.createMemberType("len", ty, b.prog.Uint(), 1), + } + }) +} + +func (b diBuilder) createArrayType(ty Type, l int64) DIType { + tyElem := b.prog.rawType(ty.RawType().(*types.Array).Elem()) + return &aDIType{ll: b.di.CreateArrayType(llvm.DIArrayType{ + SizeInBits: b.prog.SizeOf(ty) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8), + ElementType: b.diType(tyElem, token.Position{}).ll, + Subscripts: []llvm.DISubrange{{ + Count: l, + }}, + })} +} + +func (b diBuilder) createSliceType(name string, ty, tyElem Type) DIType { + pos := token.Position{} + diElemTyPtr := b.prog.Pointer(tyElem) + + return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata { + return []llvm.Metadata{ + b.createMemberTypeEx("data", ty, diElemTyPtr, 0, pos, 0), + b.createMemberTypeEx("len", ty, b.prog.Uint(), 1, pos, 0), + b.createMemberTypeEx("cap", ty, b.prog.Uint(), 2, pos, 0), + } + }) +} + +func (b diBuilder) createInterfaceType(name string, ty Type) DIType { + tyRaw := ty.RawType().Underlying() + tyIntr := b.prog.rawType(tyRaw) + tyType := b.prog.VoidPtr() + tyData := b.prog.VoidPtr() + + return b.doCreateStructType(name, tyIntr, token.Position{}, func(ditStruct DIType) []llvm.Metadata { + return []llvm.Metadata{ + b.createMemberType("type", ty, tyType, 0), + b.createMemberType("data", ty, tyData, 1), + } + }) +} + +func (b diBuilder) createMemberType(name string, tyStruct, tyField Type, idxField int) llvm.Metadata { + return b.createMemberTypeEx(name, tyStruct, tyField, idxField, token.Position{}, 0) +} + +func (b diBuilder) createMemberTypeEx(name string, tyStruct, tyField Type, idxField int, pos token.Position, flags int) llvm.Metadata { + return b.di.CreateMemberType( + b.diType(tyStruct, pos).ll, + llvm.DIMemberType{ + Name: name, + SizeInBits: b.prog.SizeOf(tyField) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(tyField.RawType()) * 8), + OffsetInBits: b.prog.OffsetOf(tyStruct, idxField) * 8, + Type: b.diType(tyField, pos).ll, + Flags: flags, + }, + ) +} + +func (b diBuilder) createMapType(name string, tyMap Type, pos token.Position) DIType { + // ty := tyMap.RawType().(*types.Map) + // tk := b.prog.rawType(ty.Key()) + // tv := b.prog.rawType(ty.Elem()) + tyCount := b.prog.Int() + return b.doCreateStructType(name, tyMap, pos, func(ditStruct DIType) []llvm.Metadata { + return []llvm.Metadata{ + b.createMemberType("count", tyMap, tyCount, 0), + } + }) +} + +func (b diBuilder) createChanType(name string, t Type, pos token.Position) DIType { + return b.doCreateStructType(name, t, pos, func(ditStruct DIType) []llvm.Metadata { + return []llvm.Metadata{} + }) +} + +func (b diBuilder) createComplexType(t Type) DIType { + var tfield Type + var tyName string + if t.RawType().(*types.Basic).Kind() == types.Complex128 { + tfield = b.prog.Float64() + tyName = "complex128" + } else { + tfield = b.prog.Float32() + tyName = "complex64" + } + return b.doCreateStructType(tyName, t, token.Position{}, func(ditStruct DIType) []llvm.Metadata { + return []llvm.Metadata{ + b.createMemberType("real", t, tfield, 0), + b.createMemberType("imag", t, tfield, 1), + } + }) +} + +func (b diBuilder) createPointerType(name string, ty Type, pos token.Position) DIType { + return &aDIType{ll: b.di.CreatePointerType(llvm.DIPointerType{ + Name: name, + Pointee: b.diType(ty, pos).ll, + SizeInBits: b.prog.SizeOf(ty) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType())) * 8, + AddressSpace: 0, + })} +} + +func (b diBuilder) doCreateStructType(name string, ty Type, pos token.Position, fn func(ty DIType) []llvm.Metadata) (ret DIType) { + structType := ty.RawType().Underlying() + + scope := b.file(pos.Filename) + ret = &aDIType{b.di.CreateReplaceableCompositeType( + scope.ll, + llvm.DIReplaceableCompositeType{ + Tag: dwarf.TagStructType, + Name: name, + File: b.file(pos.Filename).ll, + Line: pos.Line, + SizeInBits: b.prog.SizeOf(ty) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(structType) * 8), + }, + )} + b.types[ty] = ret + + fields := fn(ret) + + st := b.di.CreateStructType( + scope.ll, + llvm.DIStructType{ + Name: name, + File: b.file(pos.Filename).ll, + Line: pos.Line, + SizeInBits: b.prog.SizeOf(ty) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(structType) * 8), + Elements: fields, + }, + ) + ret.ll.ReplaceAllUsesWith(st) + ret.ll = st + return +} + +func (b diBuilder) createStructType(name string, ty Type, pos token.Position) (ret DIType) { + structType := ty.RawType().(*types.Struct) + return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata { + fields := make([]llvm.Metadata, structType.NumFields()) + for i := 0; i < structType.NumFields(); i++ { + field := structType.Field(i) + tyField := b.prog.rawType(field.Type()) + flags := 0 + pos := b.positioner.Position(field.Pos()) + fields[i] = b.createMemberTypeEx(field.Name(), ty, tyField, i, pos, flags) + } + return fields + }) +} + +func (b diBuilder) createFuncPtrType(name string, ty Type, pos token.Position) DIType { + ptr := b.prog.VoidPtr() + return &aDIType{ll: b.di.CreatePointerType(llvm.DIPointerType{ + Name: name, + Pointee: b.diType(ptr, pos).ll, + SizeInBits: b.prog.SizeOf(ptr) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ptr.RawType()) * 8), + })} +} + +// ---------------------------------------------------------------------------- + +func (b diBuilder) dbgDeclare(v Expr, dv DIVar, scope DIScope, pos token.Position, expr DIExpression, blk BasicBlock) { + loc := llvm.DebugLoc{ + Line: uint(pos.Line), + Col: uint(pos.Column), + Scope: scope.scopeMeta(b, pos).ll, + } + b.di.InsertDeclareAtEnd( + v.impl, + dv.ll, + expr.ll, + loc, + blk.last, + ) +} + +func (b diBuilder) dbgValue(v Expr, dv DIVar, scope DIScope, pos token.Position, expr DIExpression, blk BasicBlock) { + loc := llvm.DebugLoc{ + Line: uint(pos.Line), + Col: uint(pos.Column), + Scope: scope.scopeMeta(b, pos).ll, + } + b.di.InsertValueAtEnd( + v.impl, + dv.ll, + expr.ll, + loc, + blk.last, + ) +} + +func (b diBuilder) diType(t Type, pos token.Position) DIType { + name := t.RawType().String() + return b.diTypeEx(name, t, pos) +} + +func (b diBuilder) diTypeEx(name string, t Type, pos token.Position) DIType { + if ty, ok := b.types[t]; ok { + return ty + } + ty := b.createType(name, t, pos) + b.types[t] = ty + return ty +} + +func (b diBuilder) varParam(f Function, pos token.Position, varName string, vt DIType, argNo int) DIVar { + return b.createParameterVariable( + f, + pos, + varName, + argNo, + vt, + ) +} + +func (b diBuilder) varAuto(f Function, pos token.Position, varName string, vt DIType) DIVar { + return b.createAutoVariable(f, pos, varName, vt) +} + +func (b diBuilder) file(filename string) DIFile { + return b.createFile(filename) +} + +// ---------------------------------------------------------------------------- + +type aDIExpression struct { + ll llvm.Metadata +} + +type DIExpression = *aDIExpression + +func (b diBuilder) createExpression(ops []uint64) DIExpression { + return &aDIExpression{b.di.CreateExpression(ops)} +} + +// ----------------------------------------------------------------------------- + +// Copy to alloca'd memory to get declareable address. +func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, deref bool) { + if v, ok := b.dbgVars[v]; ok { + return v.ptr, v.val, v.deref + } + t := v.Type.RawType().Underlying() + dbgPtr, dbgVal, deref = b.doConstructDebugAddr(v, t) + b.dbgVars[v] = dbgExpr{dbgPtr, dbgVal, deref} + return dbgPtr, dbgVal, deref +} + +func (b Builder) doConstructDebugAddr(v Expr, t types.Type) (dbgPtr Expr, dbgVal Expr, deref bool) { + var ty Type + switch t := t.(type) { + case *types.Pointer: + return v, v, false + case *types.Basic: + if t.Info()&types.IsComplex != 0 { + if t.Kind() == types.Complex128 { + ty = b.Prog.Complex128() + } else { + ty = b.Prog.Complex64() + } + } else if t.Info()&types.IsString != 0 { + ty = b.Prog.rtType("String") + } else { + ty = v.Type + } + case *types.Struct: + ty = v.Type + case *types.Slice: + ty = b.Prog.Type(b.Prog.rtType("Slice").RawType().Underlying(), InGo) + case *types.Signature: + ty = b.Prog.Closure(b.Prog.rawType(t)) + case *types.Named: + ty = b.Prog.Type(t.Underlying(), InGo) + case *types.Map: + ty = b.Prog.Type(b.Prog.rtType("Map").RawType().Underlying(), InGo) + default: + ty = v.Type + } + dbgPtr = b.AllocaT(ty) + dbgPtr.Type = b.Prog.Pointer(v.Type) + b.Store(dbgPtr, v) + dbgVal = b.Load(dbgPtr) + return dbgPtr, dbgVal, deref +} + +func (b Builder) di() diBuilder { + return b.Pkg.di +} + +func (b Builder) DIDeclare(v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) { + dbgPtr, _, _ := b.constructDebugAddr(v) + expr := b.di().createExpression(nil) + b.di().dbgDeclare(dbgPtr, dv, scope, pos, expr, blk) +} + +func (b Builder) DIValue(v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) { + expr := b.di().createExpression(nil) + b.di().dbgValue(v, dv, scope, pos, expr, blk) +} + +func (b Builder) DIVarParam(f Function, pos token.Position, varName string, vt Type, argNo int) DIVar { + t := b.di().diType(vt, pos) + return b.di().varParam(f, pos, varName, t, argNo) +} + +func (b Builder) DIVarAuto(f Function, pos token.Position, varName string, vt Type) DIVar { + t := b.di().diType(vt, pos) + return b.di().varAuto(f, pos, varName, t) +} + +func (b Builder) DIGlobal(v Expr, name string, pos token.Position) { + if _, ok := b.Pkg.glbDbgVars[v]; ok { + return + } + gv := b.di().createGlobalVariableExpression( + b.Pkg.cu, + pos, + name, + name, + v.Type, + false, + ) + v.impl.AddMetadata(0, gv.ll) + b.Pkg.glbDbgVars[v] = true +} + +func (b Builder) DISetCurrentDebugLocation(f Function, pos token.Position) { + b.impl.SetCurrentDebugLocation( + uint(pos.Line), + uint(pos.Column), + f.scopeMeta(b.di(), pos).ll, + f.impl.InstructionDebugLoc(), + ) +} + +func (b Builder) DebugFunction(f Function, pos token.Position) { + // attach debug info to function + f.scopeMeta(b.Pkg.di, pos) +} + +func (b Builder) Param(idx int) Expr { + p := b.Func.Param(idx) + if v, ok := b.dbgVars[p]; ok { + return v.val + } + return p +} + +// ----------------------------------------------------------------------------- diff --git a/ssa/memory.go b/ssa/memory.go index d9f469e9..e0a46b5a 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -144,6 +144,16 @@ func (b Builder) Alloca(n Expr) (ret Expr) { return } +func (b Builder) AllocaT(t Type) (ret Expr) { + if debugInstr { + log.Printf("AllocaT %v\n", t.RawType()) + } + prog := b.Prog + ret.impl = llvm.CreateAlloca(b.impl, t.ll) + ret.Type = prog.Pointer(t) + return +} + /* TODO(xsw): // AllocaU allocates uninitialized space for n*sizeof(elem) bytes. func (b Builder) AllocaU(elem Type, n ...int64) (ret Expr) { diff --git a/ssa/package.go b/ssa/package.go index 4554b56d..dd0f3032 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -347,12 +347,15 @@ func (p Program) NewPackage(name, pkgPath string) Package { pymods := make(map[string]Global) strs := make(map[string]llvm.Value) named := make(map[types.Type]Expr) + glbDbgVars := make(map[Expr]bool) p.NeedRuntime = false // Don't need reset p.needPyInit here // p.needPyInit = false ret := &aPackage{ mod: mod, vars: gbls, fns: fns, stubs: stubs, - pyobjs: pyobjs, pymods: pymods, strs: strs, named: named, Prog: p} + pyobjs: pyobjs, pymods: pymods, strs: strs, named: named, Prog: p, + di: nil, cu: nil, glbDbgVars: glbDbgVars, + } ret.abi.Init(pkgPath) return ret } @@ -590,6 +593,10 @@ type aPackage struct { Prog Program + di diBuilder + cu CompilationUnit + glbDbgVars map[Expr]bool + vars map[string]Global fns map[string]Function stubs map[string]Function @@ -706,6 +713,11 @@ func (p Package) AfterInit(b Builder, ret BasicBlock) { } } +func (p Package) InitDebugSymbols(name, pkgPath string, positioner Positioner) { + p.di = newDIBuilder(p.Prog, p, positioner) + p.cu = p.di.createCompileUnit(name, pkgPath) +} + // ----------------------------------------------------------------------------- /* diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index f837c3fd..042a5687 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -57,12 +57,20 @@ func (p BasicBlock) Addr() Expr { // ----------------------------------------------------------------------------- +type dbgExpr struct { + ptr Expr + val Expr + deref bool +} + type aBuilder struct { impl llvm.Builder blk BasicBlock Func Function Pkg Package Prog Program + + dbgVars map[Expr]dbgExpr } // Builder represents a builder for creating instructions in a function.