Merge pull request #806 from cpunion/llvm-debug

Debug: fix struct vars debug, params modication, supports expressions, lexical scope/lifecycle
This commit is contained in:
xushiwei
2024-10-29 11:09:00 +08:00
committed by GitHub
19 changed files with 861 additions and 293 deletions

View File

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

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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] <pkg> [pkgPath]")
return
}
llgen.Init(build.IsDebugEnabled())
llgen.Init()
args := os.Args[1:]
llgen.SmartDoFile(args[0], args[1:]...)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

298
ssa/di.go
View File

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

View File

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

View File

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

View File

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