Merge pull request #794 from cpunion/llvm-debug

ssa: add llvm debug info
This commit is contained in:
xushiwei
2024-09-22 12:08:16 +08:00
committed by GitHub
24 changed files with 2075 additions and 72 deletions

View File

@@ -98,6 +98,11 @@ jobs:
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
run: bash .github/workflows/test_demo.sh

115
_lldb/README.md Normal file
View File

@@ -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 = <variable not available>
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 = <variable not available>
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}
```

38
_lldb/common.sh Normal file
View File

@@ -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}"
}

246
_lldb/llgo_plugin.py Normal file
View File

@@ -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 "<variable not available>"
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 "<variable not available>"
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 "<variable not available>"
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

12
_lldb/runlldb.sh Executable file
View File

@@ -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"

56
_lldb/runtest.sh Executable file
View File

@@ -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"

390
_lldb/test.py Normal file
View File

@@ -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()

View File

@@ -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...)
}
}

View File

@@ -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 <pkg> [pkgPath]")
fmt.Fprintln(os.Stderr, "Usage: llgen [flags] <pkg> [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:]...)
}

View File

@@ -0,0 +1 @@
-dbg

323
cl/_testdata/debug/in.go Normal file
View File

@@ -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

View File

@@ -0,0 +1 @@
;

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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]

View File

@@ -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}
}
// -----------------------------------------------------------------------------

623
ssa/di.go Normal file
View File

@@ -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
}
// -----------------------------------------------------------------------------

View File

@@ -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) {

View File

@@ -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)
}
// -----------------------------------------------------------------------------
/*

View File

@@ -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.