diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 05bf769e..f4ca26ba 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -99,9 +99,10 @@ jobs: bash .github/workflows/test_llgo.sh - name: LLDB tests + if: ${{startsWith(matrix.os, 'macos')}} run: | echo "Test lldb with llgo plugin on ${{matrix.os}} with LLVM ${{matrix.llvm}}" - bash _lldb/runtest.sh + bash _lldb/runtest.sh -v - name: Test demos continue-on-error: true diff --git a/.gitignore b/.gitignore index e7faa0ec..3300f527 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ build.dir/ # Test binary, built with `go test -c` *.test +# Debug symbols +*.dSYM + # Output of the go coverage tool, specifically when used with LiteIDE *.out *.swp diff --git a/_lldb/common.sh b/_lldb/common.sh index 94dfe334..f1376e21 100644 --- a/_lldb/common.sh +++ b/_lldb/common.sh @@ -26,6 +26,8 @@ find_lldb() { # Find LLDB 18+ LLDB_PATH=$(find_lldb) +echo "LLDB_PATH: $LLDB_PATH" +$LLDB_PATH --version export LLDB_PATH # Default package path @@ -34,5 +36,5 @@ 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}" + LLGO_DEBUG=1 go run ./cmd/llgo build -o "${package_path}/debug.out" "${package_path}" } diff --git a/_lldb/llgo_plugin.py b/_lldb/llgo_plugin.py index d45a1a5d..4643fe76 100644 --- a/_lldb/llgo_plugin.py +++ b/_lldb/llgo_plugin.py @@ -1,70 +1,112 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring +from typing import List, Optional, Dict, Any, Tuple import re import lldb -def __lldb_init_module(debugger, _): +def log(*args: Any, **kwargs: Any) -> None: + print(*args, **kwargs, flush=True) + + +def __lldb_init_module(debugger: lldb.SBDebugger, _: Dict[str, Any]) -> None: 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): +def is_llgo_compiler(_target: lldb.SBTarget) -> bool: 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 +def get_indexed_value(value: lldb.SBValue, index: int) -> Optional[lldb.SBValue]: + if not value or not value.IsValid(): + return None - frame = debugger.GetSelectedTarget().GetProcess( - ).GetSelectedThread().GetSelectedFrame() + type_name = value.GetType().GetName() - # 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) + if type_name.startswith('[]'): # Slice + data_ptr = value.GetChildMemberWithName('data') + element_type = data_ptr.GetType().GetPointeeType() + element_size = element_type.GetByteSize() + ptr_value = int(data_ptr.GetValue(), 16) + element_address = ptr_value + index * element_size + target = value.GetTarget() + return target.CreateValueFromAddress( + f"element_{index}", lldb.SBAddress(element_address, target), element_type) + elif value.GetType().IsArrayType(): # Array + return value.GetChildAtIndex(index) else: - result.AppendMessage(f"Error: {var.error}") + return None -def print_all_variables(debugger, command, result, _internal_dict): +def evaluate_expression(frame: lldb.SBFrame, expression: str) -> Optional[lldb.SBValue]: + parts = re.findall(r'\*|\w+|\(|\)|\[.*?\]|\.', expression) + + def evaluate_part(i: int) -> Tuple[Optional[lldb.SBValue], int]: + nonlocal parts + value: Optional[lldb.SBValue] = None + while i < len(parts): + part = parts[i] + + if part == '*': + sub_value, i = evaluate_part(i + 1) + if sub_value and sub_value.IsValid(): + value = sub_value.Dereference() + else: + return None, i + elif part == '(': + depth = 1 + j = i + 1 + while j < len(parts) and depth > 0: + if parts[j] == '(': + depth += 1 + elif parts[j] == ')': + depth -= 1 + j += 1 + value, i = evaluate_part(i + 1) + i = j - 1 + elif part == ')': + return value, i + 1 + elif part == '.': + if value is None: + value = frame.FindVariable(parts[i+1]) + else: + value = value.GetChildMemberWithName(parts[i+1]) + i += 2 + elif part.startswith('['): + index = int(part[1:-1]) + value = get_indexed_value(value, index) + i += 1 + else: + if value is None: + value = frame.FindVariable(part) + else: + value = value.GetChildMemberWithName(part) + i += 1 + + if not value or not value.IsValid(): + return None, i + + return value, i + + value, _ = evaluate_part(0) + return value + + +def print_go_expression(debugger: lldb.SBDebugger, command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None: + frame = debugger.GetSelectedTarget().GetProcess( + ).GetSelectedThread().GetSelectedFrame() + value = evaluate_expression(frame, command) + if value and value.IsValid(): + result.AppendMessage(format_value(value, debugger)) + else: + result.AppendMessage( + f"Error: Unable to evaluate expression '{command}'") + + +def print_all_variables(debugger: lldb.SBDebugger, _command: str, result: lldb.SBCommandReturnObject, _internal_dict: Dict[str, Any]) -> None: target = debugger.GetSelectedTarget() if not is_llgo_compiler(target): result.AppendMessage("Not a LLGo compiled binary.") @@ -72,9 +114,9 @@ def print_all_variables(debugger, command, result, _internal_dict): frame = debugger.GetSelectedTarget().GetProcess( ).GetSelectedThread().GetSelectedFrame() - variables = frame.GetVariables(True, True, True, False) + variables = frame.GetVariables(True, True, True, True) - output = [] + output: List[str] = [] for var in variables: type_name = map_type_name(var.GetType().GetName()) formatted = format_value(var, debugger, include_type=False, indent=0) @@ -83,14 +125,12 @@ def print_all_variables(debugger, command, result, _internal_dict): result.AppendMessage("\n".join(output)) -def is_pointer(frame, var_name): +def is_pointer(frame: lldb.SBFrame, var_name: str) -> bool: 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): +def format_value(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0) -> str: if not var.IsValid(): return "" @@ -98,8 +138,15 @@ def format_value(var, debugger, include_type=True, indent=0): type_class = var_type.GetTypeClass() type_name = map_type_name(var_type.GetName()) + # Handle typedef types + original_type_name = type_name + while var_type.IsTypedefType(): + var_type = var_type.GetTypedefedType() + type_name = map_type_name(var_type.GetName()) + type_class = var_type.GetTypeClass() + if var_type.IsPointerType(): - return format_pointer(var, debugger, indent, type_name) + return format_pointer(var, debugger, indent, original_type_name) if type_name.startswith('[]'): # Slice return format_slice(var, debugger, indent) @@ -108,7 +155,7 @@ def format_value(var, debugger, include_type=True, indent=0): 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) + return format_struct(var, debugger, include_type, indent, original_type_name) else: value = var.GetValue() summary = var.GetSummary() @@ -120,10 +167,13 @@ def format_value(var, debugger, include_type=True, indent=0): return "" -def format_slice(var, debugger, indent): - length = int(var.GetChildMemberWithName('len').GetValue()) +def format_slice(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str: + length = var.GetChildMemberWithName('len').GetValue() + if length is None: + return "" + length = int(length) data_ptr = var.GetChildMemberWithName('data') - elements = [] + elements: List[str] = [] ptr_value = int(data_ptr.GetValue(), 16) element_type = data_ptr.GetType().GetPointeeType() @@ -152,8 +202,8 @@ def format_slice(var, debugger, indent): return result -def format_array(var, debugger, indent): - elements = [] +def format_array(var: lldb.SBValue, debugger: lldb.SBDebugger, indent: int) -> str: + elements: List[str] = [] indent_str = ' ' * indent next_indent_str = ' ' * (indent + 1) @@ -166,27 +216,28 @@ def format_array(var, debugger, indent): element_type = map_type_name(var.GetType().GetArrayElementType().GetName()) type_name = f"[{array_size}]{element_type}" - if len(elements) > 5: # 如果元素数量大于5,则进行折行显示 + if len(elements) > 5: # wrap line if too many elements 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): +def format_string(var: lldb.SBValue) -> str: 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()) + length = var.GetChildMemberWithName('len').GetValue() if data and length: + length = int(length) error = lldb.SBError() return '"%s"' % var.process.ReadCStringFromMemory(int(data, 16), length + 1, error) - return '""' + return "" -def format_struct(var, debugger, include_type=True, indent=0, type_name=""): - children = [] +def format_struct(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: bool = True, indent: int = 0, type_name: str = "") -> str: + children: List[str] = [] indent_str = ' ' * indent next_indent_str = ' ' * (indent + 1) @@ -209,13 +260,13 @@ def format_struct(var, debugger, include_type=True, indent=0, type_name=""): return struct_content -def format_pointer(var, debugger, indent, type_name): +def format_pointer(var: lldb.SBValue, _debugger: lldb.SBDebugger, _indent: int, _type_name: str) -> str: if not var.IsValid() or var.GetValueAsUnsigned() == 0: return "" return var.GetValue() # Return the address as a string -def map_type_name(type_name): +def map_type_name(type_name: str) -> str: # Handle pointer types if type_name.endswith('*'): base_type = type_name[:-1].strip() @@ -223,7 +274,7 @@ def map_type_name(type_name): return f"*{mapped_base_type}" # Map other types - type_mapping = { + type_mapping: Dict[str, str] = { 'long': 'int', 'void': 'unsafe.Pointer', 'char': 'byte', diff --git a/_lldb/runlldb.sh b/_lldb/runlldb.sh index 39da6860..0e6e57df 100755 --- a/_lldb/runlldb.sh +++ b/_lldb/runlldb.sh @@ -8,5 +8,8 @@ source "$(dirname "$0")/common.sh" executable="$1" -# Run LLDB -"$LLDB_PATH" "$executable" +# Get the directory of the current script +script_dir="$(dirname "$0")" + +# Run LLDB with the LLGO plugin +"$LLDB_PATH" -O "command script import ${script_dir}/llgo_plugin.py" "$executable" diff --git a/_lldb/runtest.sh b/_lldb/runtest.sh index c1047ac9..6d73cbb8 100755 --- a/_lldb/runtest.sh +++ b/_lldb/runtest.sh @@ -4,7 +4,8 @@ set -e # Source common functions and variables # shellcheck source=./_lldb/common.sh -source "$(dirname "$0")/common.sh" +# shellcheck disable=SC1091 +source "$(dirname "$0")/common.sh" || exit 1 # Parse command-line arguments package_path="$DEFAULT_PACKAGE_PATH" @@ -34,23 +35,35 @@ while [[ $# -gt 0 ]]; do done # Build the project -build_project "$package_path" +build_project "$package_path" || exit 1 + +# Set up the result file path +result_file="/tmp/lldb_exit_code" # Prepare LLDB commands lldb_commands=( + "command script import _lldb/llgo_plugin.py" "command script import _lldb/test.py" - "script test.run_tests(\\\"${package_path}/out\\\", [\\\"${package_path}/in.go\\\"], ${verbose}, ${interactive}, ${plugin_path})" + "script test.run_tests_with_result('${package_path}/debug.out', ['${package_path}/in.go'], $verbose, $interactive, $plugin_path, '$result_file')" + "quit" ) -# 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\"" + lldb_command_string+=" -o \"$cmd\"" done + +# Run LLDB with the test script eval "$LLDB_PATH $lldb_command_string" + +# Read the exit code from the result file +if [ -f "$result_file" ]; then + exit_code=$(cat "$result_file") + rm "$result_file" + exit "$exit_code" +else + echo "Error: Could not find exit code file" + exit 1 +fi diff --git a/_lldb/test.py b/_lldb/test.py index f7bfcab8..306914ca 100644 --- a/_lldb/test.py +++ b/_lldb/test.py @@ -7,17 +7,14 @@ import signal from dataclasses import dataclass, field from typing import List, Optional, Set, Dict, Any import lldb -import llgo_plugin # Add this import +import llgo_plugin +from llgo_plugin import log class LLDBTestException(Exception): pass -def log(*args: Any, **kwargs: Any) -> None: - print(*args, **kwargs, flush=True) - - @dataclass class Test: source_file: str @@ -60,7 +57,7 @@ class TestResults: class LLDBDebugger: - def __init__(self, executable_path: str, plugin_path: Optional[str] = None): + def __init__(self, executable_path: str, plugin_path: Optional[str] = None) -> None: self.executable_path: str = executable_path self.plugin_path: Optional[str] = plugin_path self.debugger: lldb.SBDebugger = lldb.SBDebugger.Create() @@ -70,7 +67,6 @@ class LLDBDebugger: self.type_mapping: Dict[str, str] = { 'long': 'int', 'unsigned long': 'uint', - # Add more mappings as needed } def setup(self) -> None: @@ -104,29 +100,14 @@ class LLDBDebugger: 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 + value = llgo_plugin.evaluate_expression(frame, var_expression) + if value and value.IsValid(): + return llgo_plugin.format_value(value, self.debugger) + return 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)) + return set(var.GetName() for var in frame.GetVariables(True, True, True, True)) def get_current_function_name(self) -> str: frame = self.process.GetSelectedThread().GetFrameAtIndex(0) @@ -189,7 +170,7 @@ class LLDBDebugger: def parse_expected_values(source_files: List[str]) -> List[TestCase]: - test_cases = [] + test_cases: List[TestCase] = [] for source_file in source_files: with open(source_file, 'r', encoding='utf-8') as f: content = f.readlines() @@ -198,7 +179,7 @@ def parse_expected_values(source_files: List[str]) -> List[TestCase]: line = content[i].strip() if line.startswith('// Expected:'): start_line = i + 1 - tests = [] + tests: List[Test] = [] i += 1 while i < len(content): line = content[i].strip() @@ -224,7 +205,8 @@ def execute_tests(executable_path: str, test_cases: List[TestCase], verbose: boo debugger = LLDBDebugger(executable_path, plugin_path) try: if verbose: - log(f"Setting breakpoint at {test_case.source_file}: {test_case.end_line}") + log( + f"\nSetting 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() @@ -259,7 +241,7 @@ def execute_tests(executable_path: str, test_cases: List[TestCase], verbose: boo return results -def run_tests(executable_path: str, source_files: List[str], verbose: bool, interactive: bool, plugin_path: Optional[str]) -> None: +def run_tests(executable_path: str, source_files: List[str], verbose: bool, interactive: bool, plugin_path: Optional[str]) -> int: test_cases = parse_expected_values(source_files) if verbose: log(f"Running tests for {', '.join(source_files)} with {executable_path}") @@ -269,12 +251,12 @@ def run_tests(executable_path: str, source_files: List[str], verbose: bool, inte verbose, interactive, plugin_path) print_test_results(results) - if results.total != results.passed: - os._exit(1) + # Return 0 if all tests passed, 1 otherwise + return 0 if results.failed == 0 else 1 def execute_test_case(debugger: LLDBDebugger, test_case: TestCase, all_variable_names: Set[str]) -> CaseResult: - results = [] + results: List[TestResult] = [] for test in test_case.tests: if test.variable == "all variables": @@ -367,6 +349,24 @@ def print_test_result(result: TestResult, verbose: bool) -> None: log(f" Actual: {result.actual}") +def run_tests_with_result(executable_path: str, source_files: List[str], verbose: bool, interactive: bool, plugin_path: Optional[str], result_path: str) -> int: + try: + exit_code = run_tests(executable_path, source_files, + verbose, interactive, plugin_path) + except Exception as e: + log(f"An error occurred during test execution: {str(e)}") + exit_code = 2 # Use a different exit code for unexpected errors + + try: + with open(result_path, 'w', encoding='utf-8') as f: + f.write(str(exit_code)) + except IOError as e: + log(f"Error writing result to file {result_path}: {str(e)}") + # If we can't write to the file, we should still return the exit code + + return exit_code + + def main() -> None: log(sys.argv) parser = argparse.ArgumentParser( @@ -378,12 +378,24 @@ def main() -> None: 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") + parser.add_argument("--result-path", help="Path to write the result") 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) + + try: + if args.result_path: + exit_code = run_tests_with_result(args.executable, args.sources, + args.verbose, args.interactive, plugin_path, args.result_path) + else: + exit_code = run_tests(args.executable, args.sources, + args.verbose, args.interactive, plugin_path) + except Exception as e: + log(f"An unexpected error occurred: {str(e)}") + exit_code = 2 # Use a different exit code for unexpected errors + + sys.exit(exit_code) if __name__ == "__main__": diff --git a/chore/llgen/llgen.go b/chore/llgen/llgen.go index d93e8d9d..4f14892b 100644 --- a/chore/llgen/llgen.go +++ b/chore/llgen/llgen.go @@ -20,7 +20,6 @@ import ( "fmt" "os" - "github.com/goplus/llgo/internal/build" "github.com/goplus/llgo/internal/llgen" ) @@ -29,7 +28,7 @@ func main() { fmt.Fprintln(os.Stderr, "Usage: llgen [flags] [pkgPath]") return } - llgen.Init(build.IsDebugEnabled()) + llgen.Init() args := os.Args[1:] llgen.SmartDoFile(args[0], args[1:]...) } diff --git a/cl/_testdata/debug/in.go b/cl/_testdata/debug/in.go index bba503c6..5840a92e 100644 --- a/cl/_testdata/debug/in.go +++ b/cl/_testdata/debug/in.go @@ -78,9 +78,10 @@ func FuncWithAllTypeStructParam(s StructWithAllTypeFields) { // 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' + s.i8 = '\b' + // Expected: + // s.i8: '\b' + // s.i16: 2 println(len(s.s), s.i8) } @@ -115,6 +116,36 @@ func FuncWithAllTypeParams( err error, fn func(string) (int, error), ) (int, error) { + // 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 + // i32: 3 + // i64: 4 + // i: 5 + // u32: 8 + // u64: 9 + // u: 10 + // f32: 11 + // f64: 12 + // 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}} + // slice[0]: 21 + // slice[1]: 22 + // slice[2]: 23 + // arr[0]: 24 + // arr[1]: 25 + // arr[2]: 26 + // arr2[0].i: 27 + // arr2[1].i: 28 + // arr2[2].i: 29 + // e: github.com/goplus/llgo/cl/_testdata/debug.E{i = 30} + + // Expected(skip): + // i8: '\b' + // i16: 2 + // u8: '\x06' + // u16: 7 + // b: true println( i8, i16, i32, i64, i, u8, u16, u32, u64, u, f32, f64, b, @@ -127,32 +158,61 @@ func FuncWithAllTypeParams( 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 + i16 = 10 + i32 = 11 + i64 = 12 + i = 13 + u8 = 14 + u16 = 15 + u32 = 16 + u64 = 17 + u = 18 + f32 = 19 + f64 = 20 + b = false + c64 = 21 + 22i + c128 = 23 + 24i + slice = []int{31, 32, 33} + arr = [3]int{34, 35, 36} + arr2 = [3]E{{i: 37}, {i: 38}, {i: 39}} + s = "world" + e = E{i: 40} + + println(i8, i16, i32, i64, i, u8, u16, u32, u64, u, + f32, f64, b, + c64, c128, + slice, arr[0:], &arr2, + s, + &e, + &f, pf, pi, intr, m, + c, + err, + fn, + ) + // Expected: + // i8: '\t' + // i16: 10 + // i32: 11 + // i64: 12 + // i: 13 + // u8: '\x0e' + // u16: 15 + // u32: 16 + // u64: 17 + // u: 18 + // f32: 19 + // f64: 20 + // b: false + // c64: complex64{real = 21, imag = 22} + // c128: complex128{real = 23, imag = 24} + // slice: []int{31, 32, 33} + // arr2: [3]github.com/goplus/llgo/cl/_testdata/debug.E{{i = 37}, {i = 38}, {i = 39}} + // s: "world" + // e: github.com/goplus/llgo/cl/_testdata/debug.E{i = 40} + // Expected(skip): - // i8: '\x09' - println(i8) + // arr: [3]int{34, 35, 36} return 1, errors.New("some error") } @@ -185,7 +245,7 @@ type BigStruct struct { } func FuncStructParams(t TinyStruct, s SmallStruct, m MidStruct, b BigStruct) { - println(&t, &s, &m, &b) + // println(&t, &s, &m, &b) // Expected: // all variables: t s m b // t.I: 1 @@ -204,15 +264,45 @@ func FuncStructParams(t TinyStruct, s SmallStruct, m MidStruct, b BigStruct) { // b.P: 14 // b.Q: 15 // b.R: 16 + println(t.I, s.I, s.J, m.I, m.J, m.K, b.I, b.J, b.K, b.L, b.M, b.N, b.O, b.P, b.Q, b.R) t.I = 10 - // Expected(skip): + s.I = 20 + s.J = 21 + m.I = 40 + m.J = 41 + m.K = 42 + b.I = 70 + b.J = 71 + b.K = 72 + b.L = 73 + b.M = 74 + b.N = 75 + b.O = 76 + b.P = 77 + b.Q = 78 + b.R = 79 + // Expected: // all variables: t s m b // t.I: 10 + // s.I: 20 + // s.J: 21 + // m.I: 40 + // m.J: 41 + // m.K: 42 + // b.I: 70 + // b.J: 71 + // b.K: 72 + // b.L: 73 + // b.M: 74 + // b.N: 75 + // b.O: 76 + // b.P: 77 + // b.Q: 78 + // b.R: 79 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 @@ -231,13 +321,139 @@ func FuncStructPtrParams(t *TinyStruct, s *SmallStruct, m *MidStruct, b *BigStru // b.P: 14 // b.Q: 15 // b.R: 16 + println(t, s, m, b) t.I = 10 + s.I = 20 + s.J = 21 + m.I = 40 + m.J = 41 + m.K = 42 + b.I = 70 + b.J = 71 + b.K = 72 + b.L = 73 + b.M = 74 + b.N = 75 + b.O = 76 + b.P = 77 + b.Q = 78 + b.R = 79 // Expected: // all variables: t s m b // t.I: 10 + // s.I: 20 + // s.J: 21 + // m.I: 40 + // m.J: 41 + // m.K: 42 + // b.I: 70 + // b.J: 71 + // b.K: 72 + // b.L: 73 + // b.M: 74 + // b.N: 75 + // b.O: 76 + // b.P: 77 + // b.Q: 78 + // b.R: 79 + println(t.I, s.I, s.J, m.I, m.J, m.K, b.I, b.J, b.K, b.L, b.M, b.N, b.O, b.P, b.Q, b.R) println("done") } +func ScopeIf(branch int) { + a := 1 + // Expected: + // all variables: a branch + // a: 1 + if branch == 1 { + b := 2 + c := 3 + // Expected: + // all variables: a b c branch + // a: 1 + // b: 2 + // c: 3 + // branch: 1 + println(a, b, c) + } else { + c := 3 + d := 4 + // Expected: + // all variables: a c d branch + // a: 1 + // c: 3 + // d: 4 + // branch: 0 + println(a, c, d) + } + // Expected: + // all variables: a branch + // a: 1 + println("a:", a) +} + +func ScopeFor() { + a := 1 + for i := 0; i < 10; i++ { + switch i { + case 0: + println("i is 0") + // Expected: + // all variables: i a + // i: 0 + // a: 1 + println("i:", i) + case 1: + println("i is 1") + // Expected: + // all variables: i a + // i: 1 + // a: 1 + println("i:", i) + default: + println("i is", i) + } + } + println("a:", a) +} + +func ScopeSwitch(i int) { + a := 0 + switch i { + case 1: + b := 1 + println("i is 1") + // Expected: + // all variables: i a b + // i: 1 + // a: 0 + // b: 1 + println("i:", i, "a:", a, "b:", b) + case 2: + c := 2 + println("i is 2") + // Expected: + // all variables: i a c + // i: 2 + // a: 0 + // c: 2 + println("i:", i, "a:", a, "c:", c) + default: + d := 3 + println("i is", i) + // Expected: + // all variables: i a d + // i: 3 + // a: 0 + // d: 3 + println("i:", i, "a:", a, "d:", d) + } + // Expected: + // all variables: a i + // a: 0 + println("a:", a) +} + 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}) @@ -263,7 +479,7 @@ func main() { arr2: [3]E{{i: 27}, {i: 28}, {i: 29}}, s: "hello", e: E{i: 30}, - pf: &StructWithAllTypeFields{}, + pf: &StructWithAllTypeFields{i16: 100}, pi: &i, intr: &Struct{}, m: map[string]uint64{"a": 31, "b": 32}, @@ -277,9 +493,36 @@ func main() { pad1: 100, pad2: 200, } + // Expected: + // all variables: s i err + // 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.pf.i16: 100 + // *(s.pf).i16: 100 + // *(s.pi): 100 globalStructPtr = &s globalStruct = s println("globalInt:", globalInt) + // Expected(skip): + // all variables: globalInt globalStruct globalStructPtr s i err println("s:", &s) FuncWithAllTypeStructParam(s) println("called function with struct") @@ -298,18 +541,21 @@ func main() { s.fn, ) println(i, err) - println("called function with types") + ScopeIf(1) + ScopeIf(0) + ScopeFor() + ScopeSwitch(1) + ScopeSwitch(2) + ScopeSwitch(3) 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 + // Expected: + // all variables: s i err // s.i8: '\x12' + + // Expected(skip): // globalStruct.i8: '\x01' println((*globalStructPtr).i8) println("done") diff --git a/cl/compile.go b/cl/compile.go index cb697c7e..42c2d2da 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -238,6 +238,9 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun sig = types.NewSignatureType(nil, nil, nil, params, results, false) } fn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), hasCtx, f.Origin() != nil) + if debugSymbols { + fn.Inline(llssa.NoInline) + } } if nblk := len(f.Blocks); nblk > 0 { @@ -260,8 +263,9 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun } b := fn.NewBuilder() if debugSymbols { - b.DebugFunction(fn, p.goProg.Fset.Position(f.Pos())) - b.DISetCurrentDebugLocation(p.fn, p.goProg.Fset.Position(f.Pos())) + pos := p.goProg.Fset.Position(f.Pos()) + bodyPos := p.getFuncBodyPos(f) + b.DebugFunction(fn, pos, bodyPos) } p.bvals = make(map[ssa.Value]llssa.Expr) off := make([]int, len(f.Blocks)) @@ -291,14 +295,56 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun return fn, nil, goFunc } +func (p *context) getFuncBodyPos(f *ssa.Function) token.Position { + if f.Object() != nil { + return p.goProg.Fset.Position(f.Object().(*types.Func).Scope().Pos()) + } + return p.goProg.Fset.Position(f.Pos()) +} + +func isGlobal(v *types.Var) bool { + // TODO(lijie): better implementation + return strings.HasPrefix(v.Parent().String(), "package ") +} + +func (p *context) debugRef(b llssa.Builder, v *ssa.DebugRef) { + object := v.Object() + variable, ok := object.(*types.Var) + if !ok { + // Not a local variable. + return + } + if variable.IsField() { + // skip *ssa.FieldAddr + return + } + if isGlobal(variable) { + // avoid generate local variable debug info of global variable in function + return + } + pos := p.goProg.Fset.Position(v.Pos()) + value := p.compileValue(b, v.X) + fn := v.Parent() + dbgVar := p.getLocalVariable(b, fn, variable) + scope := variable.Parent() + diScope := b.DIScope(p.fn, scope) + if v.IsAddr { + // *ssa.Alloc + b.DIDeclare(variable, value, dbgVar, diScope, pos, b.Func.Block(v.Block().Index)) + } else { + b.DIValue(variable, value, dbgVar, diScope, pos, b.Func.Block(v.Block().Index)) + } +} + func (p *context) debugParams(b llssa.Builder, f *ssa.Function) { for i, param := range f.Params { + variable := param.Object().(*types.Var) 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)) + b.DIParam(variable, v, div, p.fn, pos, p.fn.Block(0)) } } @@ -483,11 +529,6 @@ 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) @@ -655,11 +696,27 @@ func (p *context) jumpTo(v *ssa.Jump) llssa.BasicBlock { return fn.Block(succs[0].Index) } +func (p *context) getDebugLocScope(v *ssa.Function, pos token.Pos) *types.Scope { + if v.Object() == nil { + return nil + } + funcScope := v.Object().(*types.Func).Scope() + return funcScope.Innermost(pos) +} + func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { if iv, ok := instr.(instrOrValue); ok { p.compileInstrOrValue(b, iv, false) return } + if debugSymbols { + scope := p.getDebugLocScope(instr.Parent(), instr.Pos()) + if scope != nil { + diScope := b.DIScope(p.fn, scope) + pos := p.fset.Position(instr.Pos()) + b.DISetCurrentDebugLocation(diScope, pos) + } + } switch v := instr.(type) { case *ssa.Store: va := v.Addr @@ -720,26 +777,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { 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)) - } + p.debugRef(b, v) } default: panic(fmt.Sprintf("compileInstr: unknown instr - %T\n", instr)) @@ -755,7 +793,8 @@ func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.V return b.DIVarParam(p.fn, pos, v.Name(), t, argNo) } } - return b.DIVarAuto(p.fn, pos, v.Name(), t) + scope := b.DIScope(p.fn, v.Parent()) + return b.DIVarAuto(scope, pos, v.Name(), t) } func (p *context) compileFunction(v *ssa.Function) (goFn llssa.Function, pyFn llssa.PyObjRef, kind int) { diff --git a/cl/instr.go b/cl/instr.go index 5c7f9b2f..ff1c30c1 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -273,6 +273,9 @@ func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyObj } sig := fn.Signature aFn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false, fn.Origin() != nil) + if debugSymbols { + aFn.Inline(llssa.NoInline) + } } } return diff --git a/internal/build/build.go b/internal/build/build.go index 7e970a78..8a81727f 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -187,7 +187,14 @@ func Do(args []string, conf *Config) { return dedup.Check(llssa.PkgPython).Types }) - progSSA := ssa.NewProgram(initial[0].Fset, ssaBuildMode) + buildMode := ssaBuildMode + if cl.DebugSymbols() { + buildMode |= ssa.GlobalDebug + } + if !IsOptimizeEnabled() { + buildMode |= ssa.NaiveForm + } + progSSA := ssa.NewProgram(initial[0].Fset, buildMode) patches := make(cl.Patches, len(altPkgPaths)) altSSAPkgs(progSSA, patches, altPkgs[1:], verbose) @@ -237,7 +244,7 @@ func isNeedRuntimeOrPyInit(pkg *packages.Package) (needRuntime, needPyInit bool) } const ( - ssaBuildMode = ssa.SanityCheckFunctions | ssa.InstantiateGenerics | ssa.GlobalDebug + ssaBuildMode = ssa.SanityCheckFunctions | ssa.InstantiateGenerics ) type context struct { @@ -438,7 +445,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, llFiles } args = append(args, exargs...) if cl.DebugSymbols() { - args = append(args, "-gdwarf-5") + args = append(args, "-gdwarf-4") } // TODO(xsw): show work @@ -609,10 +616,22 @@ var ( ) const llgoDebug = "LLGO_DEBUG" +const llgoOptimize = "LLGO_OPTIMIZE" + +func isEnvOn(env string, defVal bool) bool { + envVal := strings.ToLower(os.Getenv(env)) + if envVal == "" { + return defVal + } + return envVal == "1" || envVal == "true" || envVal == "on" +} func IsDebugEnabled() bool { - llgoDbgVal := strings.ToLower(os.Getenv(llgoDebug)) - return llgoDbgVal == "1" || llgoDbgVal == "true" || llgoDbgVal == "on" + return isEnvOn(llgoDebug, false) +} + +func IsOptimizeEnabled() bool { + return isEnvOn(llgoOptimize, true) } func ParseArgs(args []string, swflags map[string]bool) (flags, patterns []string, verbose bool) { diff --git a/internal/llgen/llgen.go b/internal/llgen/llgen.go index 9b6de2e6..14440c42 100644 --- a/internal/llgen/llgen.go +++ b/internal/llgen/llgen.go @@ -20,16 +20,17 @@ import ( "os" "github.com/goplus/llgo/cl" + "github.com/goplus/llgo/internal/build" "github.com/goplus/llgo/internal/mod" llssa "github.com/goplus/llgo/ssa" ) -func Init(enableDbg bool) { +func Init() { llssa.Initialize(llssa.InitAll) llssa.SetDebug(llssa.DbgFlagAll) cl.SetDebug(cl.DbgFlagAll) - cl.EnableDebugSymbols(enableDbg) + cl.EnableDebugSymbols(build.IsDebugEnabled()) } func PkgPath(dir string) string { diff --git a/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index a55bbfb5..cf55c856 100644 --- a/internal/llgen/llgenf.go +++ b/internal/llgen/llgenf.go @@ -25,6 +25,7 @@ import ( "strings" "github.com/goplus/llgo/cl" + "github.com/goplus/llgo/internal/build" "github.com/goplus/llgo/internal/packages" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" @@ -90,7 +91,14 @@ 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|ssa.GlobalDebug) + buildMode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics + if build.IsDebugEnabled() { + buildMode |= ssa.GlobalDebug + } + if !build.IsOptimizeEnabled() { + buildMode |= ssa.NaiveForm + } + _, pkgs := ssautil.AllPackages(initial, buildMode) pkg := initial[0] ssaPkg := pkgs[0] diff --git a/ssa/decl.go b/ssa/decl.go index 471224bf..1e82a74b 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -17,7 +17,6 @@ package ssa import ( - "go/token" "go/types" "log" "strconv" @@ -281,7 +280,8 @@ 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, make(map[Expr]dbgExpr)} + return &aBuilder{b, nil, p, p.Pkg, prog, + make(map[Expr]dbgExpr), make(map[*types.Scope]DIScope)} } // HasBody reports whether the function has a body. @@ -334,33 +334,24 @@ 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} +// ----------------------------------------------------------------------------- + +type inlineAttr int + +const ( + NoInline inlineAttr = iota + AlwaysInline + InlineHint +) + +func (p Function) Inline(inline inlineAttr) { + inlineAttrName := map[inlineAttr]string{ + NoInline: "noinline", + AlwaysInline: "alwaysinline", + InlineHint: "inlinehint", + }[inline] + inlineAttr := p.Pkg.mod.Context().CreateEnumAttribute(llvm.AttributeKindID(inlineAttrName), 0) + p.impl.AddFunctionAttr(inlineAttr) } // ----------------------------------------------------------------------------- diff --git a/ssa/di.go b/ssa/di.go index ccb0dd54..578ff25f 100644 --- a/ssa/di.go +++ b/ssa/di.go @@ -6,6 +6,7 @@ import ( "go/token" "go/types" "path/filepath" + "unsafe" "github.com/goplus/llvm" ) @@ -19,6 +20,7 @@ type aDIBuilder struct { prog Program types map[Type]DIType positioner Positioner + m llvm.Module // Add this field } type diBuilder = *aDIBuilder @@ -26,20 +28,21 @@ 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(), - }), - ) + + b := &aDIBuilder{ + di: llvm.NewDIBuilder(m), + prog: prog, + types: make(map[*aType]DIType), + positioner: positioner, + m: m, // Initialize the m field + } + + b.addNamedMetadataOperand("llvm.module.flags", 2, "Debug Info Version", 3) + b.addNamedMetadataOperand("llvm.module.flags", 7, "Dwarf Version", 4) + b.addNamedMetadataOperand("llvm.module.flags", 1, "wchar_size", 4) + b.addNamedMetadataOperand("llvm.module.flags", 8, "PIC Level", 2) + b.addNamedMetadataOperand("llvm.module.flags", 7, "uwtable", 1) + b.addNamedMetadataOperand("llvm.module.flags", 7, "frame-pointer", 1) // Add llvm.ident metadata identNode := ctx.MDNode([]llvm.Metadata{ @@ -47,12 +50,19 @@ func newDIBuilder(prog Program, pkg Package, positioner Positioner) diBuilder { }) m.AddNamedMetadataOperand("llvm.ident", identNode) - return &aDIBuilder{ - di: llvm.NewDIBuilder(m), - prog: prog, - types: make(map[*aType]DIType), - positioner: positioner, - } + return b +} + +// New method to add named metadata operand +func (b diBuilder) addNamedMetadataOperand(name string, intValue int, stringValue string, intValue2 int) { + ctx := b.m.Context() + b.m.AddNamedMetadataOperand(name, + ctx.MDNode([]llvm.Metadata{ + llvm.ConstInt(ctx.Int32Type(), uint64(intValue), false).ConstantAsMetadata(), + ctx.MDString(stringValue), + llvm.ConstInt(ctx.Int32Type(), uint64(intValue2), false).ConstantAsMetadata(), + }), + ) } // ---------------------------------------------------------------------------- @@ -77,7 +87,7 @@ func (b diBuilder) createCompileUnit(filename, dir string) CompilationUnit { File: filename, Dir: dir, Producer: "LLGo", - Optimized: false, + Optimized: true, RuntimeVersion: 1, })} } @@ -125,10 +135,9 @@ func (b diBuilder) createType(name string, ty Type, pos token.Position) DIType { 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, + Name: name, + SizeInBits: b.prog.SizeOf(b.prog.rawType(t)) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(t) * 8), }) return &aDIType{typ} } @@ -158,9 +167,8 @@ func (b diBuilder) createType(name string, ty Type, pos token.Position) DIType { 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) + // Create typedef type for named types + return b.createTypedefType(name, ty, pos) case *types.Interface: ty := b.prog.rtType("Iface") return b.createInterfaceType(name, ty) @@ -180,6 +188,8 @@ func (b diBuilder) createType(name string, ty Type, pos token.Position) DIType { case *types.Map: ty := b.prog.rtType("Map") return b.createMapType(name, ty, pos) + case *types.Tuple: + return b.createTupleType(name, ty, pos) default: panic(fmt.Errorf("can't create debug info of type: %v, %T", ty.RawType(), ty.RawType())) } @@ -194,6 +204,10 @@ type aDIFunction struct { type DIFunction = *aDIFunction +func (p Function) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta { + return &aDIScopeMeta{p.diFunc.ll} +} + // ---------------------------------------------------------------------------- type aDIGlobalVariableExpression struct { @@ -221,16 +235,28 @@ func (b diBuilder) createGlobalVariableExpression(scope DIScope, pos token.Posit // ---------------------------------------------------------------------------- +type aDILexicalBlock struct { + ll llvm.Metadata +} + +type DILexicalBlock = *aDILexicalBlock + +func (l *aDILexicalBlock) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta { + return &aDIScopeMeta{l.ll} +} + +// ---------------------------------------------------------------------------- + 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 { +func (b diBuilder) createParameterVariable(scope DIScope, pos token.Position, name string, argNo int, ty DIType) DIVar { return &aDIVar{ ll: b.di.CreateParameterVariable( - f.scopeMeta(b, pos).ll, + scope.scopeMeta(b, pos).ll, llvm.DIParameterVariable{ Name: name, File: b.file(pos.Filename).ll, @@ -258,6 +284,18 @@ func (b diBuilder) createAutoVariable(scope DIScope, pos token.Position, name st } } +func (b diBuilder) createTypedefType(name string, ty Type, pos token.Position) DIType { + underlyingType := b.diType(b.prog.rawType(ty.RawType().(*types.Named).Underlying()), pos) + typ := b.di.CreateTypedef(llvm.DITypedef{ + Name: name, + Type: underlyingType.ll, + File: b.file(pos.Filename).ll, + Line: pos.Line, + AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8), + }) + return &aDIType{typ} +} + func (b diBuilder) createStringType() DIType { ty := b.prog.rtType("String") return b.doCreateStructType("string", ty, token.Position{}, func(ditStruct DIType) []llvm.Metadata { @@ -362,12 +400,12 @@ func (b diBuilder) createComplexType(t Type) DIType { } func (b diBuilder) createPointerType(name string, ty Type, pos token.Position) DIType { + ptrType := b.prog.VoidPtr() 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, + Name: name, + Pointee: b.diType(ty, pos).ll, + SizeInBits: b.prog.SizeOf(ptrType) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ptrType.RawType())) * 8, })} } @@ -421,6 +459,26 @@ func (b diBuilder) createStructType(name string, ty Type, pos token.Position) (r }) } +func (b diBuilder) createTupleType(name string, ty Type, pos token.Position) DIType { + tupleType := ty.RawType().(*types.Tuple) + if tupleType.Len() == 0 { + return &aDIType{} + } + if tupleType.Len() == 1 { + t := b.prog.rawType(tupleType.At(0).Type()) + return b.diType(t, pos) + } + return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata { + fields := make([]llvm.Metadata, ty.RawType().(*types.Tuple).Len()) + for i := 0; i < ty.RawType().(*types.Tuple).Len(); i++ { + field := ty.RawType().(*types.Tuple).At(i) + tyField := b.prog.rawType(field.Type()) + fields[i] = b.createMemberTypeEx(field.Name(), ty, tyField, i, pos, 0) + } + 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{ @@ -477,9 +535,9 @@ func (b diBuilder) diTypeEx(name string, t Type, pos token.Position) DIType { return ty } -func (b diBuilder) varParam(f Function, pos token.Position, varName string, vt DIType, argNo int) DIVar { +func (b diBuilder) varParam(scope DIScope, pos token.Position, varName string, vt DIType, argNo int) DIVar { return b.createParameterVariable( - f, + scope, pos, varName, argNo, @@ -487,8 +545,8 @@ func (b diBuilder) varParam(f Function, pos token.Position, varName string, vt D ) } -func (b diBuilder) varAuto(f Function, pos token.Position, varName string, vt DIType) DIVar { - return b.createAutoVariable(f, pos, varName, vt) +func (b diBuilder) varAuto(scope DIScope, pos token.Position, varName string, vt DIType) DIVar { + return b.createAutoVariable(scope, pos, varName, vt) } func (b diBuilder) file(filename string) DIFile { @@ -510,21 +568,21 @@ func (b diBuilder) createExpression(ops []uint64) DIExpression { // ----------------------------------------------------------------------------- // Copy to alloca'd memory to get declareable address. -func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, deref bool) { +func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, exists bool) { if v, ok := b.dbgVars[v]; ok { - return v.ptr, v.val, v.deref + return v.ptr, v.val, true } t := v.Type.RawType().Underlying() - dbgPtr, dbgVal, deref = b.doConstructDebugAddr(v, t) - b.dbgVars[v] = dbgExpr{dbgPtr, dbgVal, deref} - return dbgPtr, dbgVal, deref + dbgPtr, dbgVal = b.doConstructDebugAddr(v, t) + dbgExpr := dbgExpr{dbgPtr, dbgVal} + b.dbgVars[v] = dbgExpr + b.dbgVars[dbgVal] = dbgExpr + return dbgPtr, dbgVal, false } -func (b Builder) doConstructDebugAddr(v Expr, t types.Type) (dbgPtr Expr, dbgVal Expr, deref bool) { +func (b Builder) doConstructDebugAddr(v Expr, t types.Type) (dbgPtr Expr, dbgVal Expr) { 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 { @@ -554,34 +612,111 @@ func (b Builder) doConstructDebugAddr(v Expr, t types.Type) (dbgPtr Expr, dbgVal dbgPtr.Type = b.Prog.Pointer(v.Type) b.Store(dbgPtr, v) dbgVal = b.Load(dbgPtr) - return dbgPtr, dbgVal, deref + return dbgPtr, dbgVal } 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) +func (b Builder) DIParam(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) { + b.DIValue(variable, v, dv, scope, pos, blk) +} + +func (b Builder) DIDeclare(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) { expr := b.di().createExpression(nil) - b.di().dbgDeclare(dbgPtr, dv, scope, pos, expr, blk) + b.di().dbgDeclare(v, 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) DIValue(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) { + ty := v.Type.RawType().Underlying() + if !needConstructAddr(ty) { + expr := b.di().createExpression(nil) + b.di().dbgValue(v, dv, scope, pos, expr, blk) + } else { + dbgPtr, _, _ := b.constructDebugAddr(v) + expr := b.di().createExpression([]uint64{opDeref}) + b.di().dbgValue(dbgPtr, dv, scope, pos, expr, blk) + } } -func (b Builder) DIVarParam(f Function, pos token.Position, varName string, vt Type, argNo int) DIVar { +const ( + opDeref = 0x06 +) + +func needConstructAddr(t types.Type) bool { + switch t := t.(type) { + case *types.Basic: + if t.Info()&types.IsComplex != 0 { + return true + } else if t.Info()&types.IsString != 0 { + return true + } + return false + case *types.Pointer: + return false + default: + return true + } +} + +func (b Builder) DIVarParam(scope DIScope, 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) + return b.di().varParam(scope, pos, varName, t, argNo) } -func (b Builder) DIVarAuto(f Function, pos token.Position, varName string, vt Type) DIVar { +func (b Builder) DIVarAuto(scope DIScope, pos token.Position, varName string, vt Type) DIVar { t := b.di().diType(vt, pos) - return b.di().varAuto(f, pos, varName, t) + return b.di().varAuto(scope, pos, varName, t) } +// hack for types.Scope +type hackScope struct { + parent *types.Scope + children []*types.Scope + number int // parent.children[number-1] is this scope; 0 if there is no parent + elems map[string]types.Object // lazily allocated + pos, end token.Pos // scope extent; may be invalid + comment string // for debugging only + isFunc bool // set if this is a function scope (internal use only) +} + +func isFunc(scope *types.Scope) bool { + hs := (*hackScope)(unsafe.Pointer(scope)) + return hs.isFunc +} + +func (b Builder) DIScope(f Function, scope *types.Scope) DIScope { + if cachedScope, ok := b.diScopeCache[scope]; ok { + return cachedScope + } + pos := b.di().positioner.Position(scope.Pos()) + // skip package and universe scope + // if scope.Parent().Parent() == nil { + // return b.di().file(pos.Filename) + // } + + var result DIScope + if isFunc(scope) { + // TODO(lijie): should check scope == function scope + result = f + } else { + parentScope := b.DIScope(f, scope.Parent()) + result = &aDILexicalBlock{b.di().di.CreateLexicalBlock(parentScope.scopeMeta(b.di(), pos).ll, llvm.DILexicalBlock{ + File: b.di().file(pos.Filename).ll, + Line: pos.Line, + Column: pos.Column, + })} + } + + b.diScopeCache[scope] = result + return result +} + +const ( + MD_dbg = 0 +) + func (b Builder) DIGlobal(v Expr, name string, pos token.Position) { if _, ok := b.Pkg.glbDbgVars[v]; ok { return @@ -594,22 +729,55 @@ func (b Builder) DIGlobal(v Expr, name string, pos token.Position) { v.Type, false, ) - v.impl.AddMetadata(0, gv.ll) + v.impl.AddMetadata(MD_dbg, gv.ll) b.Pkg.glbDbgVars[v] = true } -func (b Builder) DISetCurrentDebugLocation(f Function, pos token.Position) { +func (b Builder) DISetCurrentDebugLocation(diScope DIScope, pos token.Position) { b.impl.SetCurrentDebugLocation( uint(pos.Line), uint(pos.Column), - f.scopeMeta(b.di(), pos).ll, - f.impl.InstructionDebugLoc(), + diScope.scopeMeta(b.di(), pos).ll, + llvm.Metadata{}, ) } -func (b Builder) DebugFunction(f Function, pos token.Position) { - // attach debug info to function - f.scopeMeta(b.Pkg.di, pos) +func (b Builder) DebugFunction(f Function, pos token.Position, bodyPos token.Position) { + p := f + if p.diFunc == nil { + sig := p.Type.raw.Type.(*types.Signature) + rt := p.Prog.Type(sig.Results(), InGo) + paramTypes := make([]llvm.Metadata, len(p.params)+1) + paramTypes[0] = b.di().diType(rt, pos).ll + for i, t := range p.params { + paramTypes[i+1] = b.di().diType(t, pos).ll + } + diFuncType := b.di().di.CreateSubroutineType(llvm.DISubroutineType{ + File: b.di().file(pos.Filename).ll, + Parameters: paramTypes, + }) + dif := llvm.DIFunction{ + Type: diFuncType, + Name: p.Name(), + LinkageName: p.Name(), + File: b.di().file(pos.Filename).ll, + Line: pos.Line, + ScopeLine: bodyPos.Line, + IsDefinition: true, + LocalToUnit: true, + Optimized: true, + } + p.diFunc = &aDIFunction{ + b.di().di.CreateFunction(b.di().file(pos.Filename).ll, dif), + } + p.impl.SetSubprogram(p.diFunc.ll) + } + b.impl.SetCurrentDebugLocation( + uint(bodyPos.Line), + uint(bodyPos.Column), + p.diFunc.ll, + f.impl.InstructionDebugLoc(), + ) } func (b Builder) Param(idx int) Expr { diff --git a/ssa/eh.go b/ssa/eh.go index 902aabac..2e30e867 100644 --- a/ssa/eh.go +++ b/ssa/eh.go @@ -141,6 +141,10 @@ const ( ) func (b Builder) getDefer(kind DoAction) *aDefer { + if b.Func.recov == nil { + // b.Func.recov maybe nil in ssa.NaiveForm + return nil + } self := b.Func if self.defer_ == nil { // TODO(xsw): check if in pkg.init @@ -241,6 +245,9 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) { // RunDefers emits instructions to run deferred instructions. func (b Builder) RunDefers() { self := b.getDefer(DeferInCond) + if self == nil { + return + } blk := b.Func.MakeBlock() self.rundsNext = append(self.rundsNext, blk) diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index 042a5687..10128ce6 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -58,9 +58,8 @@ func (p BasicBlock) Addr() Expr { // ----------------------------------------------------------------------------- type dbgExpr struct { - ptr Expr - val Expr - deref bool + ptr Expr + val Expr } type aBuilder struct { @@ -70,7 +69,8 @@ type aBuilder struct { Pkg Package Prog Program - dbgVars map[Expr]dbgExpr + dbgVars map[Expr]dbgExpr // save copied address and values for debug info + diScopeCache map[*types.Scope]DIScope // avoid duplicated DILexicalBlock(s) } // Builder represents a builder for creating instructions in a function. diff --git a/ssa/type_cvt.go b/ssa/type_cvt.go index b696cfeb..5abc75c2 100644 --- a/ssa/type_cvt.go +++ b/ssa/type_cvt.go @@ -109,6 +109,8 @@ func (p goTypes) cvtType(typ types.Type) (raw types.Type, cvt bool) { if elem, cvt := p.cvtType(t.Elem()); cvt { return types.NewChan(t.Dir(), elem), true } + case *types.Tuple: + return p.cvtTuple(t) default: panic(fmt.Sprintf("cvtType: unexpected type - %T", typ)) }