diff --git a/cmd/internal/lldb/lldb_test.go b/cmd/internal/lldb/lldb_test.go index 1c4fc3379f..45709005e7 100644 --- a/cmd/internal/lldb/lldb_test.go +++ b/cmd/internal/lldb/lldb_test.go @@ -188,6 +188,10 @@ func TestEmbeddedPluginIdentity(t *testing.T) { "SliceSyntheticProvider", "interface_summary", "function_summary", + "map_summary", + "MapSyntheticProvider", + "channel_summary", + "ChannelSyntheticProvider", "llgo status", "llgo print", "llgo vars", diff --git a/cmd/internal/lldb/llgo_plugin.py b/cmd/internal/lldb/llgo_plugin.py index 534b459d9c..cf4d213982 100644 --- a/cmd/internal/lldb/llgo_plugin.py +++ b/cmd/internal/lldb/llgo_plugin.py @@ -14,6 +14,7 @@ LLGO_MAX_STRING_SUMMARY_BYTES = 256 LLGO_MAX_TYPE_NAME_BYTES = 4096 LLGO_DEFAULT_MAX_CHILDREN = 256 +LLGO_MAX_CONTAINER_SCAN_BUCKETS = 65536 _TARGET_INFO_CACHE: Dict[Tuple[Any, ...], "LLGoTargetInfo"] = {} @@ -93,6 +94,31 @@ class LLGoRuntimeLayout: function_data: str function_closure_symbol_pattern: str function_bound_symbol_suffix: str + map_type_pattern: str + map_count: str + map_flags: str + map_same_size_grow_flag: int + map_bucket_bits: str + map_buckets: str + map_old_buckets: str + map_bucket_tophash: str + map_bucket_keys: str + map_bucket_indirect_keys: str + map_bucket_values: str + map_bucket_indirect_values: str + map_bucket_overflow: str + map_evacuated_tophash_min: int + map_evacuated_tophash_max: int + map_occupied_tophash_min: int + channel_type_pattern: str + channel_count: str + channel_capacity: str + channel_buffer: str + channel_closed: str + channel_receive_index: str + channel_receive_queue: str + channel_queue_first: str + channel_waiter_element: str @dataclass(frozen=True) @@ -104,6 +130,17 @@ class LLGoSliceValue: element_size: int +@dataclass(frozen=True) +class LLGoChannelValue: + length: int + capacity: int + buffer: int + receive_index: int + closed: bool + element_type: lldb.SBType + element_size: int + + def _runtime_layouts() -> Dict[int, LLGoRuntimeLayout]: layouts = {} for version, raw in LLGO_DEBUGGER_SCHEMA.get( @@ -113,6 +150,8 @@ def _runtime_layouts() -> Dict[int, LLGoRuntimeLayout]: interface_layout = raw.get("interface", {}) runtime_type_layout = raw.get("runtime_type", {}) function_layout = raw.get("function", {}) + map_layout = raw.get("map", {}) + channel_layout = raw.get("channel", {}) try: layouts[int(version)] = LLGoRuntimeLayout( string_type=string_layout["type_name"], @@ -141,6 +180,37 @@ def _runtime_layouts() -> Dict[int, LLGoRuntimeLayout]: function_layout["closure_symbol_pattern"]), function_bound_symbol_suffix=( function_layout["bound_symbol_suffix"]), + map_type_pattern=map_layout["type_pattern"], + map_count=map_layout["count"], + map_flags=map_layout["flags"], + map_same_size_grow_flag=map_layout[ + "same_size_grow_flag"], + map_bucket_bits=map_layout["bucket_bits"], + map_buckets=map_layout["buckets"], + map_old_buckets=map_layout["old_buckets"], + map_bucket_tophash=map_layout["bucket_tophash"], + map_bucket_keys=map_layout["bucket_keys"], + map_bucket_indirect_keys=( + map_layout["bucket_indirect_keys"]), + map_bucket_values=map_layout["bucket_values"], + map_bucket_indirect_values=( + map_layout["bucket_indirect_values"]), + map_bucket_overflow=map_layout["bucket_overflow"], + map_evacuated_tophash_min=map_layout[ + "evacuated_tophash_min"], + map_evacuated_tophash_max=map_layout[ + "evacuated_tophash_max"], + map_occupied_tophash_min=map_layout[ + "occupied_tophash_min"], + channel_type_pattern=channel_layout["type_pattern"], + channel_count=channel_layout["count"], + channel_capacity=channel_layout["capacity"], + channel_buffer=channel_layout["buffer"], + channel_closed=channel_layout["closed"], + channel_receive_index=channel_layout["receive_index"], + channel_receive_queue=channel_layout["receive_queue"], + channel_queue_first=channel_layout["queue_first"], + channel_waiter_element=channel_layout["waiter_element"], ) except (KeyError, TypeError, ValueError): continue @@ -242,6 +312,30 @@ def register_type_formatters(debugger: lldb.SBDebugger) -> None: lldb.SBTypeSummary.CreateWithFunctionName( "llgo_plugin.function_summary", _type_options()), ) + map_specifier = lldb.SBTypeNameSpecifier( + layout.map_type_pattern, True) + category.AddTypeSummary( + map_specifier, + lldb.SBTypeSummary.CreateWithFunctionName( + "llgo_plugin.map_summary", _type_options()), + ) + category.AddTypeSynthetic( + map_specifier, + lldb.SBTypeSynthetic.CreateWithClassName( + "llgo_plugin.MapSyntheticProvider", _type_options()), + ) + channel_specifier = lldb.SBTypeNameSpecifier( + layout.channel_type_pattern, True) + category.AddTypeSummary( + channel_specifier, + lldb.SBTypeSummary.CreateWithFunctionName( + "llgo_plugin.channel_summary", _type_options()), + ) + category.AddTypeSynthetic( + channel_specifier, + lldb.SBTypeSynthetic.CreateWithClassName( + "llgo_plugin.ChannelSyntheticProvider", _type_options()), + ) category.SetEnabled(True) @@ -547,7 +641,10 @@ def _value_as_int(value: lldb.SBValue) -> Optional[int]: try: return int(raw, 0) except (TypeError, ValueError): - return None + error = value.GetError() + if error and error.Fail(): + return None + return value.GetValueAsUnsigned() def _raw_value(value: lldb.SBValue) -> lldb.SBValue: @@ -564,6 +661,17 @@ def _canonical_type_name(value: lldb.SBValue) -> str: return value_type.GetName() if value_type and value_type.IsValid() else "" +def _matches_type_pattern(value: lldb.SBValue, pattern: str) -> bool: + value_type = _raw_value(value).GetType() + while value_type and value_type.IsValid(): + if re.fullmatch(pattern, value_type.GetName() or ""): + return True + if not value_type.IsTypedefType(): + return False + value_type = value_type.GetTypedefedType() + return False + + def _runtime_layout(value: lldb.SBValue) -> Optional[LLGoRuntimeLayout]: if not value or not value.IsValid(): return None @@ -856,6 +964,338 @@ def function_summary(value: lldb.SBValue, return name +def _pointer_runtime_value(value: lldb.SBValue, pattern: str + ) -> Tuple[Optional[int], Optional[lldb.SBValue]]: + raw = _raw_value(value) + if not _matches_type_pattern(raw, pattern): + return None, None + address = _value_as_int(raw) + if address is None or address == 0: + return address, None + pointee = raw.Dereference() + if not pointee or not pointee.IsValid(): + return address, None + pointee = pointee.GetNonSyntheticValue() + return address, pointee if pointee and pointee.IsValid() else None + + +def map_summary(value: lldb.SBValue, + _internal_dict: Dict[str, Any]) -> Optional[str]: + layout = _runtime_layout(value) + if layout is None: + return None + address, hash_value = _pointer_runtime_value( + value, layout.map_type_pattern) + if address is None: + return None + if address == 0: + return "nil" + if hash_value is None: + return None + length = _value_as_int(hash_value.GetChildMemberWithName( + layout.map_count)) + return f"len={length}" if length is not None and length >= 0 else None + + +def _type_field(value_type: lldb.SBType, name: str) -> Optional[lldb.SBType]: + while value_type and value_type.IsValid() and value_type.IsTypedefType(): + value_type = value_type.GetTypedefedType() + if not value_type or not value_type.IsValid(): + return None + for index in range(value_type.GetNumberOfFields()): + field = value_type.GetFieldAtIndex(index) + if field.GetName() == name: + field_type = field.GetType() + return field_type if field_type and field_type.IsValid() else None + return None + + +def _channel_fields(value: lldb.SBValue, + layout: LLGoRuntimeLayout) -> Optional[LLGoChannelValue]: + address, channel = _pointer_runtime_value( + value, layout.channel_type_pattern) + if address is None or address == 0 or channel is None: + return None + length = _value_as_int(channel.GetChildMemberWithName( + layout.channel_count)) + capacity = _value_as_int(channel.GetChildMemberWithName( + layout.channel_capacity)) + buffer = _value_as_int(channel.GetChildMemberWithName( + layout.channel_buffer)) + receive_index = _value_as_int(channel.GetChildMemberWithName( + layout.channel_receive_index)) + closed_value = channel.GetChildMemberWithName(layout.channel_closed) + if (length is None or capacity is None or buffer is None or + receive_index is None or length < 0 or capacity < length or + (capacity != 0 and receive_index >= capacity) or + not closed_value or not closed_value.IsValid()): + return None + + channel_type = channel.GetType() + queue_type = _type_field(channel_type, layout.channel_receive_queue) + first_type = (_type_field(queue_type, layout.channel_queue_first) + if queue_type else None) + waiter_type = (first_type.GetPointeeType() + if first_type and first_type.IsPointerType() else None) + element_pointer = (_type_field( + waiter_type, layout.channel_waiter_element) + if waiter_type else None) + element_type = (element_pointer.GetPointeeType() + if element_pointer and element_pointer.IsPointerType() + else None) + if not element_type or not element_type.IsValid(): + return None + element_size = element_type.GetByteSize() + if element_size <= 0 or (length != 0 and buffer == 0): + return None + return LLGoChannelValue( + length=length, + capacity=capacity, + buffer=buffer, + receive_index=receive_index, + closed=closed_value.GetValueAsUnsigned(0) != 0, + element_type=element_type, + element_size=element_size, + ) + + +def channel_summary(value: lldb.SBValue, + _internal_dict: Dict[str, Any]) -> Optional[str]: + layout = _runtime_layout(value) + if layout is None: + return None + address, _ = _pointer_runtime_value(value, layout.channel_type_pattern) + if address is None: + return None + if address == 0: + return "nil" + fields = _channel_fields(value, layout) + if fields is None: + return None + suffix = " closed" if fields.closed else "" + return f"len={fields.length} cap={fields.capacity}{suffix}" + + +def _renamed_value(value: lldb.SBValue, name: str) -> Optional[lldb.SBValue]: + if not value or not value.IsValid(): + return None + renamed = value.Clone(name) + return renamed if renamed and renamed.IsValid() else None + + +def _map_bucket_value(target: lldb.SBTarget, address: int, + bucket_type: lldb.SBType) -> Optional[lldb.SBValue]: + if address == 0: + return None + bucket = target.CreateValueFromAddress( + "__llgo_bucket", lldb.SBAddress(address, target), bucket_type) + return bucket if bucket and bucket.IsValid() else None + + +def _map_bucket_evacuated(target: lldb.SBTarget, address: int, + bucket_type: lldb.SBType, + layout: LLGoRuntimeLayout) -> bool: + bucket = _map_bucket_value(target, address, bucket_type) + if bucket is None: + return False + tophash = bucket.GetChildMemberWithName(layout.map_bucket_tophash) + first = tophash.GetChildAtIndex(0) + value = _value_as_int(first) + return (value is not None and + layout.map_evacuated_tophash_min <= value <= + layout.map_evacuated_tophash_max) + + +def _map_entries(value: lldb.SBValue, layout: LLGoRuntimeLayout, + max_entries: int) -> Optional[List[lldb.SBValue]]: + _, hash_value = _pointer_runtime_value(value, layout.map_type_pattern) + if hash_value is None: + return [] + length = _value_as_int(hash_value.GetChildMemberWithName( + layout.map_count)) + flags = _value_as_int(hash_value.GetChildMemberWithName( + layout.map_flags)) + bucket_bits = _value_as_int(hash_value.GetChildMemberWithName( + layout.map_bucket_bits)) + buckets = hash_value.GetChildMemberWithName(layout.map_buckets) + old_buckets = hash_value.GetChildMemberWithName(layout.map_old_buckets) + buckets_address = _value_as_int(buckets) + old_buckets_address = _value_as_int(old_buckets) + if (length is None or flags is None or bucket_bits is None or + buckets_address is None or old_buckets_address is None or + length < 0 or bucket_bits < 0 or bucket_bits >= 63): + return None + if length == 0: + return [] + if buckets_address == 0: + return None + bucket_type = buckets.GetType().GetPointeeType() + if not bucket_type or not bucket_type.IsValid(): + return None + bucket_size = bucket_type.GetByteSize() + if bucket_size <= 0: + return None + + target = value.GetTarget() + logical_buckets = 1 << bucket_bits + scan_buckets = min(logical_buckets, LLGO_MAX_CONTAINER_SCAN_BUCKETS) + old_count = (logical_buckets + if flags & layout.map_same_size_grow_flag + else logical_buckets >> 1) + entries: List[lldb.SBValue] = [] + + def append_chain(address: int) -> None: + visited = set() + while (address and address not in visited and + len(entries) < max_entries * 2): + visited.add(address) + bucket = _map_bucket_value(target, address, bucket_type) + if bucket is None: + return + tophash = bucket.GetChildMemberWithName( + layout.map_bucket_tophash) + keys = bucket.GetChildMemberWithName(layout.map_bucket_keys) + indirect_keys = not keys or not keys.IsValid() + if indirect_keys: + keys = bucket.GetChildMemberWithName( + layout.map_bucket_indirect_keys) + values = bucket.GetChildMemberWithName( + layout.map_bucket_values) + indirect_values = not values or not values.IsValid() + if indirect_values: + values = bucket.GetChildMemberWithName( + layout.map_bucket_indirect_values) + if (not tophash.IsValid() or not keys.IsValid() or + not values.IsValid()): + return + slots = min(tophash.GetNumChildren(), keys.GetNumChildren(), + values.GetNumChildren()) + for slot in range(slots): + top = _value_as_int(tophash.GetChildAtIndex(slot)) + if (top is None or + top < layout.map_occupied_tophash_min): + continue + key = keys.GetChildAtIndex(slot) + element = values.GetChildAtIndex(slot) + if indirect_keys: + key = key.Dereference() + if indirect_values: + element = element.Dereference() + pair_index = len(entries) // 2 + key = _renamed_value(key, f"key[{pair_index}]") + element = _renamed_value(element, f"value[{pair_index}]") + if key is None or element is None: + return + entries.extend((key, element)) + if len(entries) >= max_entries * 2: + return + address = _value_as_int(bucket.GetChildMemberWithName( + layout.map_bucket_overflow)) or 0 + + for bucket_index in range(scan_buckets): + bucket_address = buckets_address + bucket_index * bucket_size + if old_buckets_address and old_count: + old_index = bucket_index & (old_count - 1) + old_address = old_buckets_address + old_index * bucket_size + if not _map_bucket_evacuated( + target, old_address, bucket_type, layout): + if bucket_index >= old_count: + continue + bucket_address = old_address + append_chain(bucket_address) + if len(entries) >= min(length, max_entries) * 2: + break + return entries + + +class MapSyntheticProvider: + def __init__(self, value: lldb.SBValue, + _internal_dict: Dict[str, Any]) -> None: + self.value = value + self.raw = _raw_value(value) + self.layout = _runtime_layout(self.raw) + self.entries: Optional[List[lldb.SBValue]] = None + self.update() + + def update(self) -> bool: + self.raw = _raw_value(self.value) + self.entries = (_map_entries( + self.raw, self.layout, LLGO_DEFAULT_MAX_CHILDREN // 2) + if self.layout else None) + return False + + def num_children(self, max_children: Optional[int] = None) -> int: + count = (len(self.entries) if self.entries is not None + else self.raw.GetNumChildren()) + if max_children is not None and max_children >= 0: + count = min(count, max_children) + return count + + def get_child_at_index(self, index: int) -> Optional[lldb.SBValue]: + if self.entries is None: + return self.raw.GetChildAtIndex(index) + return self.entries[index] if 0 <= index < len(self.entries) else None + + def get_child_index(self, name: str) -> int: + if self.entries is None: + return -1 + for index, child in enumerate(self.entries): + if child.GetName() == name: + return index + return -1 + + def has_children(self) -> bool: + return self.num_children() != 0 + + +class ChannelSyntheticProvider: + def __init__(self, value: lldb.SBValue, + _internal_dict: Dict[str, Any]) -> None: + self.value = value + self.raw = _raw_value(value) + self.layout = _runtime_layout(self.raw) + self.fields: Optional[LLGoChannelValue] = None + self.update() + + def update(self) -> bool: + self.raw = _raw_value(self.value) + self.fields = (_channel_fields(self.raw, self.layout) + if self.layout else None) + return False + + def num_children(self, max_children: Optional[int] = None) -> int: + count = (self.fields.length if self.fields is not None + else self.raw.GetNumChildren()) + if max_children is not None and max_children >= 0: + count = min(count, max_children) + return count + + def get_child_at_index(self, index: int) -> Optional[lldb.SBValue]: + if self.fields is None: + return self.raw.GetChildAtIndex(index) + if (index < 0 or index >= self.fields.length or + self.fields.capacity == 0 or self.fields.buffer == 0): + return None + buffer_index = (self.fields.receive_index + index) % self.fields.capacity + address = self.fields.buffer + buffer_index * self.fields.element_size + target = self.raw.GetTarget() + return target.CreateValueFromAddress( + f"[{index}]", lldb.SBAddress(address, target), + self.fields.element_type) + + def get_child_index(self, name: str) -> int: + if self.fields is None: + return -1 + match = re.fullmatch(r"\[([0-9]+)\]", name or "") + if match is None: + return -1 + index = int(match.group(1)) + return index if index < self.num_children() else -1 + + def has_children(self) -> bool: + return self.num_children() != 0 + + class SliceSyntheticProvider: def __init__(self, value: lldb.SBValue, _internal_dict: Dict[str, Any]) -> None: self.value = value @@ -1043,6 +1483,13 @@ def format_value(var: lldb.SBValue, debugger: lldb.SBDebugger, include_type: boo type_class = var_type.GetTypeClass() if var_type.IsPointerType(): + layout = _runtime_layout(var) + if (layout and + (_matches_type_pattern(var, layout.map_type_pattern) or + _matches_type_pattern(var, layout.channel_type_pattern))): + summary = var.GetSummary() + if summary is not None: + return summary return format_pointer(var, debugger, indent, original_type_name) if type_name.startswith('[]'): # Slice diff --git a/cmd/llgo/lldbtest/README.md b/cmd/llgo/lldbtest/README.md index f43c23c73e..6684d7608c 100644 --- a/cmd/llgo/lldbtest/README.md +++ b/cmd/llgo/lldbtest/README.md @@ -46,12 +46,14 @@ debugger schema, runtime-layout version, target triple, pointer size, and byte order. Unknown marker versions disable only the LLGo-specific commands; raw LLDB debugging remains available. -For recognized LLGo targets, the adapter also gives strings length-bounded -quoted summaries and slices `len`/`cap` summaries with indexed synthetic -children. These views cover named string and slice types as well as the -predeclared types. Explicit `llgo print` slice views respect LLDB's -`target.max-children-count` setting. Ordinary C targets and targets with -unknown or ambiguous LLGo markers retain LLDB's raw presentation. +For recognized LLGo targets, the adapter provides runtime-aware views for +strings, slices, interfaces, function values, maps, and channels. Maps expose +their length and typed key/value children, including indirect large entries; +channels expose length, capacity, closed state, and buffered values in receive +order. Named container types are covered as well as predeclared types. Explicit +`llgo print` slice views respect LLDB's `target.max-children-count` setting. +Ordinary C targets and targets with unknown or ambiguous LLGo markers retain +LLDB's raw presentation. The integration fixture follows LLDB's API-test style: `main.go` marks executable breakpoint lines with `LLDB_BREAK`, while `test.py` keeps the diff --git a/cmd/llgo/lldbtest/main.go b/cmd/llgo/lldbtest/main.go index 571e171174..eefcec696a 100644 --- a/cmd/llgo/lldbtest/main.go +++ b/cmd/llgo/lldbtest/main.go @@ -72,6 +72,19 @@ func Plain(value int) int { type IntFunc func(int) int +type NamedMap map[string]int +type NamedChan chan int + +type LargeKey struct { + ID int + Pad [128]byte +} + +type LargeValue struct { + Value int + Pad [128]byte +} + type InterfaceResults struct { intResult int textResult string @@ -79,6 +92,19 @@ type InterfaceResults struct { errResult string } +type ContainerResults struct { + mapValue uint64 + namedValue int + largeValue int + channelHead int + channelLen int + channelCap int + closedHead string + closedOK bool +} + +var containerResults *ContainerResults + func RuntimeInterfaceValues() { var nilAny any anyInt := any(42) @@ -115,6 +141,77 @@ func RuntimeFunctionValues() { println(plain, named, closure, bound, nilFunc, plainResult, namedResult, closureResult, boundResult) // LLDB_BREAK: function_values } +func RuntimeContainerValues() { + var nilMap map[string]uint64 + single := map[string]uint64{"answer": 42} + named := NamedMap{"named": 17} + many := make(map[int]int, 24) + for index := 0; index < 24; index++ { + many[index] = index * index + } + counter := &Counter{base: 23} + pointers := map[string]*Counter{"counter": counter} + largeKey := LargeKey{ID: 5} + large := map[LargeKey]LargeValue{ + largeKey: {Value: 29}, + } + + var nilChannel chan int + queued := make(chan int, 4) + queued <- 7 + queued <- 8 + channelHead := <-queued + queued <- 9 + namedChannel := NamedChan(make(chan int, 2)) + namedChannel <- 31 + pointerChannel := make(chan *Counter, 1) + pointerChannel <- counter + closedChannel := make(chan string, 2) + closedChannel <- "first" + closedChannel <- "remaining" + close(closedChannel) + closedHead, closedOK := <-closedChannel + + results := &ContainerResults{ + mapValue: single["answer"], + namedValue: named["named"], + largeValue: large[largeKey].Value, + channelHead: channelHead, + channelLen: len(queued), + channelCap: cap(queued), + closedHead: closedHead, + closedOK: closedOK, + } + containerResults = results + InspectContainerValues( + nilMap, single, named, many, pointers, large, + nilChannel, queued, namedChannel, pointerChannel, closedChannel, + ) +} + +func InspectContainerValues( + nilMap map[string]uint64, + single map[string]uint64, + named NamedMap, + many map[int]int, + pointers map[string]*Counter, + large map[LargeKey]LargeValue, + nilChannel chan int, + queued chan int, + namedChannel NamedChan, + pointerChannel chan *Counter, + closedChannel chan string, +) { + println( // LLDB_BREAK: container_values + nilMap, single, named, many, pointers, large, + nilChannel, queued, namedChannel, pointerChannel, closedChannel, + containerResults.mapValue, containerResults.namedValue, + containerResults.largeValue, containerResults.channelHead, + containerResults.channelLen, containerResults.channelCap, + containerResults.closedHead, containerResults.closedOK, + ) +} + func RuntimeValues() { text := "hello" empty := "" @@ -399,6 +496,7 @@ func main() { RuntimeValues() RuntimeInterfaceValues() RuntimeFunctionValues() + RuntimeContainerValues() 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, diff --git a/cmd/llgo/lldbtest/test.py b/cmd/llgo/lldbtest/test.py index c4f3a1ae6e..74b0df4f9c 100644 --- a/cmd/llgo/lldbtest/test.py +++ b/cmd/llgo/lldbtest/test.py @@ -234,6 +234,40 @@ def test_case(marker: str, expectations: List[tuple]) -> TestCase: ("closureResult", "7"), ("boundResult", "13"), ]), + test_case("container_values", [ + ("nilMap", "nil", "summary"), + ("single", "len=1", "summary"), + ("single", "len=1"), + ("single", 'key[0]="answer", value[0]=42', "synthetic"), + ("named", "len=1", "summary"), + ("named", 'key[0]="named", value[0]=17', "synthetic"), + ("many", "len=24", "summary"), + ("many", "48", "synthetic-count"), + ("pointers", "len=1", "summary"), + ("pointers", "key[0]=string, value[0]=*lldbtest.Counter", + "synthetic-types"), + ("large", "len=1", "summary"), + ("large", "key[0]=lldbtest.LargeKey, value[0]=lldbtest.LargeValue", + "synthetic-types"), + ("nilChannel", "nil", "summary"), + ("queued", "len=2 cap=4", "summary"), + ("queued", "len=2 cap=4"), + ("queued", "[0]=8, [1]=9", "synthetic"), + ("namedChannel", "len=1 cap=2", "summary"), + ("namedChannel", "[0]=31", "synthetic"), + ("pointerChannel", "len=1 cap=1", "summary"), + ("pointerChannel", "[0]=*lldbtest.Counter", "synthetic-types"), + ("closedChannel", "len=1 cap=2 closed", "summary"), + ("closedChannel", '[0]="remaining"', "synthetic"), + ("containerResults.mapValue", "42"), + ("containerResults.namedValue", "17"), + ("containerResults.largeValue", "29"), + ("containerResults.channelHead", "7"), + ("containerResults.channelLen", "2"), + ("containerResults.channelCap", "4"), + ("containerResults.closedHead", '"first"'), + ("containerResults.closedOK", "true"), + ]), test_case("struct_values_initial", STRUCT_VALUES_INITIAL), test_case("struct_values_updated", STRUCT_VALUES_UPDATED), test_case("struct_ptrs_initial", STRUCT_VALUES_INITIAL), @@ -402,6 +436,28 @@ def get_synthetic_children(self, var_expression: str) -> Optional[str]: children.append(f"{child.GetName()}={child_value}") return ", ".join(children) + def get_synthetic_child_types(self, var_expression: str) -> Optional[str]: + value = self.get_variable(var_expression) + if not value or not value.IsValid(): + return None + value = value.GetSyntheticValue() + if not value or not value.IsValid(): + return None + children: List[str] = [] + for index in range(value.GetNumChildren()): + child = value.GetChildAtIndex(index) + children.append( + f"{child.GetName()}={llgo_plugin.map_type_name(child.GetTypeName())}") + return ", ".join(children) + + def get_synthetic_child_count(self, var_expression: str) -> Optional[str]: + value = self.get_variable(var_expression) + if not value or not value.IsValid(): + return None + value = value.GetSyntheticValue() + return (str(value.GetNumChildren()) + if value and value.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, False, True)) @@ -594,6 +650,10 @@ def execute_single_variable_test(debugger: LLDBDebugger, test: Test) -> TestResu actual_value = debugger.get_variable_summary(test.variable) elif test.mode == "synthetic": actual_value = debugger.get_synthetic_children(test.variable) + elif test.mode == "synthetic-types": + actual_value = debugger.get_synthetic_child_types(test.variable) + elif test.mode == "synthetic-count": + actual_value = debugger.get_synthetic_child_count(test.variable) elif test.mode == "limited": debugger.debugger.HandleCommand( "settings set target.max-children-count 1") diff --git a/internal/build/dwarf_standard_test.go b/internal/build/dwarf_standard_test.go index a1f2b2575f..c14c0e019a 100644 --- a/internal/build/dwarf_standard_test.go +++ b/internal/build/dwarf_standard_test.go @@ -295,12 +295,7 @@ func assertDWARFCoreTypes(t *testing.T, data *dwarf.Data, cu *dwarfNode) { if array.Count != 3 || array.Type.Size() != 2 { t.Errorf("Fixed = count %d, element size %d; want 3 and 2", array.Count, array.Type.Size()) } - for _, name := range []string{"Lookup", "Queue"} { - pointer := unwrapDWARFTypedef(fields[name].Type).(*dwarf.PtrType) - if _, ok := pointer.Type.(*dwarf.VoidType); pointer.Type != nil && !ok { - t.Errorf("Sample.%s pointee = %T, want void or unspecified", name, pointer.Type) - } - } + assertDWARFRuntimeContainerTypes(t, fields) assertDWARFWordStruct(t, "string", fields["Text"].Type, ptrSize, "data", "len") assertDWARFWordStruct(t, "slice", fields["Values"].Type, ptrSize, "data", "len", "cap") @@ -353,6 +348,92 @@ func assertDWARFCoreTypes(t *testing.T, data *dwarf.Data, cu *dwarfNode) { } } +func assertDWARFRuntimeContainerTypes(t *testing.T, + fields map[string]*dwarf.StructField) { + t.Helper() + mapPointer := unwrapDWARFTypedef(fields["Lookup"].Type).(*dwarf.PtrType) + hash, ok := unwrapDWARFTypedef(mapPointer.Type).(*dwarf.StructType) + if !ok { + t.Fatalf("Sample.Lookup pointee = %T, want synthesized map struct", mapPointer.Type) + } + hashFields := dwarfStructFields(hash) + for _, name := range []string{"count", "flags", "B", "buckets", "oldbuckets"} { + if hashFields[name] == nil { + t.Errorf("map runtime field %q not found", name) + } + } + bucketPointer, ok := unwrapDWARFTypedef(hashFields["buckets"].Type).(*dwarf.PtrType) + if !ok { + t.Fatalf("map buckets type = %T, want pointer", hashFields["buckets"].Type) + } + bucket, ok := unwrapDWARFTypedef(bucketPointer.Type).(*dwarf.StructType) + if !ok { + t.Fatalf("map bucket pointee = %T, want struct", bucketPointer.Type) + } + bucketFields := dwarfStructFields(bucket) + for _, name := range []string{"tophash", "keys", "values", "overflow"} { + if bucketFields[name] == nil { + t.Errorf("map bucket field %q not found", name) + } + } + for _, name := range []string{"tophash", "keys", "values"} { + array, ok := unwrapDWARFTypedef(bucketFields[name].Type).(*dwarf.ArrayType) + if !ok || array.Count != 8 { + t.Errorf("map bucket %s = %T count %d, want [8] array", + name, bucketFields[name].Type, arrayCount(array)) + } + } + if _, ok := unwrapDWARFTypedef(bucketFields["overflow"].Type).(*dwarf.PtrType); !ok { + t.Errorf("map bucket overflow = %T, want pointer", bucketFields["overflow"].Type) + } + + channelPointer := unwrapDWARFTypedef(fields["Queue"].Type).(*dwarf.PtrType) + channel, ok := unwrapDWARFTypedef(channelPointer.Type).(*dwarf.StructType) + if !ok { + t.Fatalf("Sample.Queue pointee = %T, want synthesized channel struct", channelPointer.Type) + } + channelFields := dwarfStructFields(channel) + for _, name := range []string{ + "qcount", "dataqsiz", "buf", "closed", "recvx", "sendq", "recvq", + } { + if channelFields[name] == nil { + t.Errorf("channel runtime field %q not found", name) + } + } + queue, ok := unwrapDWARFTypedef(channelFields["recvq"].Type).(*dwarf.StructType) + if !ok { + t.Fatalf("channel recvq = %T, want synthesized waitq", channelFields["recvq"].Type) + } + queueFields := dwarfStructFields(queue) + first, ok := unwrapDWARFTypedef(queueFields["first"].Type).(*dwarf.PtrType) + if !ok { + t.Fatalf("channel recvq.first = %T, want waiter pointer", queueFields["first"].Type) + } + waiter, ok := unwrapDWARFTypedef(first.Type).(*dwarf.StructType) + if !ok { + t.Fatalf("channel waiter = %T, want struct", first.Type) + } + element, ok := unwrapDWARFTypedef(dwarfStructFields(waiter)["elem"].Type).(*dwarf.PtrType) + if !ok || element.Type == nil { + t.Errorf("channel waiter element = %T, want typed pointer", element) + } +} + +func dwarfStructFields(structure *dwarf.StructType) map[string]*dwarf.StructField { + fields := make(map[string]*dwarf.StructField, len(structure.Field)) + for _, field := range structure.Field { + fields[field.Name] = field + } + return fields +} + +func arrayCount(array *dwarf.ArrayType) int64 { + if array == nil { + return -1 + } + return array.Count +} + func assertDWARFProgramStructure(t *testing.T, data *dwarf.Data, cu *dwarfNode) { t.Helper() inspect := findDWARFNode(cu.children, dwarf.TagSubprogram, "inspect") diff --git a/internal/debugabi/schema_test.go b/internal/debugabi/schema_test.go index 7a8f6c4144..f0b83b598d 100644 --- a/internal/debugabi/schema_test.go +++ b/internal/debugabi/schema_test.go @@ -112,6 +112,21 @@ func TestSchemaV1Contract(t *testing.T) { t.Errorf("runtime layout is missing %q", category) } } + var mapLayout struct { + SameSizeGrowFlag int `json:"same_size_grow_flag"` + EvacuatedTophashMin int `json:"evacuated_tophash_min"` + EvacuatedTophashMax int `json:"evacuated_tophash_max"` + OccupiedTophashMin int `json:"occupied_tophash_min"` + } + if err := json.Unmarshal(categories["map"], &mapLayout); err != nil { + t.Fatal(err) + } + if mapLayout.SameSizeGrowFlag != 8 || + mapLayout.EvacuatedTophashMin != 2 || + mapLayout.EvacuatedTophashMax != 4 || + mapLayout.OccupiedTophashMin != 5 { + t.Fatalf("map state constants = %+v", mapLayout) + } first := SchemaV1() first[0] = 0 diff --git a/internal/debugabi/schema_v1.json b/internal/debugabi/schema_v1.json index 1b7ac4bb7a..0f6d9cea2b 100644 --- a/internal/debugabi/schema_v1.json +++ b/internal/debugabi/schema_v1.json @@ -74,6 +74,7 @@ "type_pattern": "^map\\[.+\\].+$", "count": "count", "flags": "flags", + "same_size_grow_flag": 8, "bucket_bits": "B", "buckets": "buckets", "old_buckets": "oldbuckets", @@ -82,7 +83,10 @@ "bucket_indirect_keys": "indirectkeys", "bucket_values": "values", "bucket_indirect_values": "indirectvalues", - "bucket_overflow": "overflow" + "bucket_overflow": "overflow", + "evacuated_tophash_min": 2, + "evacuated_tophash_max": 4, + "occupied_tophash_min": 5 }, "channel": { "type_pattern": "^(chan |chan<- |<-chan ).+", diff --git a/ssa/di.go b/ssa/di.go index 0422a6ccac..9994dbae78 100644 --- a/ssa/di.go +++ b/ssa/di.go @@ -8,6 +8,7 @@ import ( "github.com/goplus/llgo/internal/debugabi" "github.com/goplus/llgo/internal/debuginfo" + ssaabi "github.com/goplus/llgo/ssa/abi" "github.com/xgo-dev/llvm" ) @@ -244,9 +245,9 @@ func (b diBuilder) createType(name string, ty Type, pos token.Position) DIType { case *types.Array: return b.createArrayType(ty, t.Len()) case *types.Chan: - return b.createOpaquePointerType(name, ty) + return b.createChanType(name, ty, t) case *types.Map: - return b.createOpaquePointerType(name, ty) + return b.createMapType(name, ty, t) case *types.Tuple: return b.createTupleType(name, ty, pos) default: @@ -447,14 +448,6 @@ func (b diBuilder) createMemberTypeEx(name string, tyStruct, tyField Type, idxFi ) } -func (b diBuilder) createOpaquePointerType(name string, ty Type) DIType { - return &aDIType{ll: b.di.CreatePointerType(llvm.DIPointerType{ - Name: name, - SizeInBits: b.prog.SizeOf(ty) * 8, - AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8), - })} -} - func (b diBuilder) createPointerType(name string, ty Type, pos token.Position) DIType { ptrType := b.prog.VoidPtr() return &aDIType{ll: b.di.CreatePointerType(llvm.DIPointerType{ @@ -465,6 +458,222 @@ func (b diBuilder) createPointerType(name string, ty Type, pos token.Position) D })} } +func (b diBuilder) createMapType(name string, ty Type, mapType *types.Map) DIType { + pos := token.Position{} + ptr := b.prog.VoidPtr() + runtimeMap := b.prog.rtType("Map") + key := b.prog.rawType(mapType.Key()) + elem := b.prog.rawType(mapType.Elem()) + hashName := fmt.Sprintf("hash<%s,%s>", mapType.Key(), mapType.Elem()) + hash := b.createSyntheticStructPlaceholder(hashName, runtimeMap, pos) + hashPtr := b.di.CreatePointerType(llvm.DIPointerType{ + Name: "*" + hashName, + Pointee: hash.ll, + SizeInBits: b.prog.SizeOf(ptr) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ptr.RawType()) * 8), + }) + ret := &aDIType{ll: b.createRuntimeContainerTypedef(name, hashPtr, pos)} + // Map values may be recursive through their key or element type. Cache the + // pointer before constructing the typed bucket, as the Go linker does when + // synthesizing map DWARF. + b.types[ty] = ret + + bucket := b.createMapBucketType(mapType, key, elem, pos) + bucketPtr := b.createDIPointerType( + fmt.Sprintf("*bucket<%s,%s>", mapType.Key(), mapType.Elem()), + bucket.ll, + ) + replacements := map[string]llvm.Metadata{ + "buckets": bucketPtr, + "oldbuckets": bucketPtr, + } + b.finishSyntheticStruct(hash, hashName, runtimeMap, + b.syntheticStructFields(hash, runtimeMap, replacements, pos), pos) + return ret +} + +func (b diBuilder) createMapBucketType(mapType *types.Map, key, elem Type, + pos token.Position) DIType { + bucketStorage := b.prog.rawType(ssaabi.MapBucketType(mapType, b.prog.sizes)) + bucketStruct := bucketStorage.RawType().Underlying().(*types.Struct) + name := fmt.Sprintf("bucket<%s,%s>", key.RawType(), elem.RawType()) + bucket := b.createSyntheticStructPlaceholder(name, bucketStorage, pos) + overflow := b.createDIPointerType("*"+name, bucket.ll) + fields := make([]llvm.Metadata, bucketStruct.NumFields()) + for index := range fields { + field := bucketStruct.Field(index) + fieldName := field.Name() + fieldType := b.prog.rawType(field.Type()) + diType := b.diType(fieldType, pos).ll + switch fieldName { + case "topbits": + fieldName = "tophash" + case "keys": + if b.prog.SizeOf(key) > ssaabi.MAXKEYSIZE { + fieldName = "indirectkeys" + } + case "elems": + fieldName = "values" + if b.prog.SizeOf(elem) > ssaabi.MAXELEMSIZE { + fieldName = "indirectvalues" + } + case "overflow": + diType = overflow + } + fields[index] = b.createDIMemberType(bucket, fieldName, + b.prog.SizeOf(fieldType), b.prog.sizes.Alignof(field.Type()), + b.prog.OffsetOf(bucketStorage, index), diType) + } + b.finishSyntheticStruct(bucket, name, bucketStorage, fields, pos) + return bucket +} + +func (b diBuilder) createChanType(name string, ty Type, chanType *types.Chan) DIType { + pos := token.Position{} + ptr := b.prog.VoidPtr() + runtimeChan := b.prog.rtType("Chan") + chanName := fmt.Sprintf("hchan<%s>", chanType.Elem()) + channel := b.createSyntheticStructPlaceholder(chanName, runtimeChan, pos) + channelPtr := b.di.CreatePointerType(llvm.DIPointerType{ + Name: "*" + chanName, + Pointee: channel.ll, + SizeInBits: b.prog.SizeOf(ptr) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ptr.RawType()) * 8), + }) + ret := &aDIType{ll: b.createRuntimeContainerTypedef(name, channelPtr, pos)} + b.types[ty] = ret + + chanStruct := runtimeChan.RawType().Underlying().(*types.Struct) + queueIndex := structFieldIndex(chanStruct, "recvq") + queue := b.prog.rawType(chanStruct.Field(queueIndex).Type()) + queueStruct := queue.RawType().Underlying().(*types.Struct) + waiterPtrType := queueStruct.Field(structFieldIndex(queueStruct, "first")).Type().(*types.Pointer) + waiter := b.prog.rawType(waiterPtrType.Elem()) + + waiterName := fmt.Sprintf("sudog<%s>", chanType.Elem()) + typedWaiter := b.createSyntheticStructPlaceholder(waiterName, waiter, pos) + typedWaiterPtr := b.createDIPointerType("*"+waiterName, typedWaiter.ll) + elem := b.prog.rawType(chanType.Elem()) + elemPtr := b.createDIPointerType("*"+chanType.Elem().String(), b.diType(elem, pos).ll) + waiterReplacements := map[string]llvm.Metadata{ + "prev": typedWaiterPtr, + "next": typedWaiterPtr, + "all": typedWaiterPtr, + "ch": ret.ll, + "elem": elemPtr, + } + b.finishSyntheticStruct(typedWaiter, waiterName, waiter, + b.syntheticStructFields(typedWaiter, waiter, waiterReplacements, pos), pos) + + queueName := fmt.Sprintf("waitq<%s>", chanType.Elem()) + typedQueue := b.createSyntheticStructPlaceholder(queueName, queue, pos) + queueReplacements := map[string]llvm.Metadata{ + "first": typedWaiterPtr, + "last": typedWaiterPtr, + } + b.finishSyntheticStruct(typedQueue, queueName, queue, + b.syntheticStructFields(typedQueue, queue, queueReplacements, pos), pos) + + channelReplacements := map[string]llvm.Metadata{ + "sendq": typedQueue.ll, + "recvq": typedQueue.ll, + } + b.finishSyntheticStruct(channel, chanName, runtimeChan, + b.syntheticStructFields(channel, runtimeChan, channelReplacements, pos), pos) + return ret +} + +func (b diBuilder) createSyntheticStructPlaceholder(name string, ty Type, + pos token.Position) DIType { + scope := b.file(pos.Filename) + return &aDIType{ll: b.di.CreateReplaceableCompositeType( + scope.ll, + llvm.DIReplaceableCompositeType{ + Tag: dwarf.TagStructType, + Name: name, + File: scope.ll, + Line: pos.Line, + SizeInBits: b.prog.SizeOf(ty) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8), + }, + )} +} + +func (b diBuilder) createRuntimeContainerTypedef(name string, + typeMeta llvm.Metadata, pos token.Position) llvm.Metadata { + ptr := b.prog.VoidPtr() + return b.di.CreateTypedef(llvm.DITypedef{ + Name: name, + Type: typeMeta, + File: b.file(pos.Filename).ll, + Line: pos.Line, + AlignInBits: uint32(b.prog.sizes.Alignof(ptr.RawType()) * 8), + }) +} + +func (b diBuilder) finishSyntheticStruct(placeholder DIType, name string, ty Type, + fields []llvm.Metadata, pos token.Position) { + scope := b.file(pos.Filename) + value := b.di.CreateStructType(scope.ll, llvm.DIStructType{ + Name: name, + File: scope.ll, + Line: pos.Line, + SizeInBits: b.prog.SizeOf(ty) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8), + Elements: fields, + }) + placeholder.ll.ReplaceAllUsesWith(value) + placeholder.ll = value +} + +func (b diBuilder) syntheticStructFields(owner DIType, ty Type, + replacements map[string]llvm.Metadata, pos token.Position) []llvm.Metadata { + structure := ty.RawType().Underlying().(*types.Struct) + fields := make([]llvm.Metadata, structure.NumFields()) + for index := 0; index < structure.NumFields(); index++ { + field := structure.Field(index) + fieldType := b.prog.rawType(field.Type()) + diType := b.diType(fieldType, pos).ll + if replacement, ok := replacements[field.Name()]; ok { + diType = replacement + } + fields[index] = b.createDIMemberType(owner, field.Name(), + b.prog.SizeOf(fieldType), b.prog.sizes.Alignof(field.Type()), + b.prog.OffsetOf(ty, index), diType) + } + return fields +} + +func (b diBuilder) createDIPointerType(name string, pointee llvm.Metadata) llvm.Metadata { + ptr := b.prog.VoidPtr() + return b.di.CreatePointerType(llvm.DIPointerType{ + Name: name, + Pointee: pointee, + SizeInBits: b.prog.SizeOf(ptr) * 8, + AlignInBits: uint32(b.prog.sizes.Alignof(ptr.RawType()) * 8), + }) +} + +func (b diBuilder) createDIMemberType(owner DIType, name string, size uint64, + align int64, offset uint64, ty llvm.Metadata) llvm.Metadata { + return b.di.CreateMemberType(owner.ll, llvm.DIMemberType{ + Name: name, + SizeInBits: size * 8, + AlignInBits: uint32(align * 8), + OffsetInBits: offset * 8, + Type: ty, + }) +} + +func structFieldIndex(structure *types.Struct, name string) int { + for index := 0; index < structure.NumFields(); index++ { + if structure.Field(index).Name() == name { + return index + } + } + panic(fmt.Sprintf("runtime field %q not found in %s", name, structure)) +} + func (b diBuilder) doCreateStructType(name string, ty Type, pos token.Position, fn func(ty DIType) []llvm.Metadata) (ret DIType) { structType := ty.RawType().Underlying() diff --git a/ssa/di_debug_test.go b/ssa/di_debug_test.go index 5732096564..861ab7a23d 100644 --- a/ssa/di_debug_test.go +++ b/ssa/di_debug_test.go @@ -107,12 +107,14 @@ func TestDebugGoTypeEncodings(t *testing.T) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "types.go", `package p type Named int64 +type Large [129]byte type Recursive struct { Next *Recursive } type Shape struct { Complex complex128 Text string Values []Named Lookup map[string]Named + LargeLookup map[Large]Large Queue chan Named Callback func(Named) (Named, error) Any any @@ -157,7 +159,14 @@ type Shape struct { "DW_ATE_complex_float", "!DISubroutineType", `name: "map[string]example.com/p.Named"`, + `name: "hash"`, + `name: "bucket"`, + `name: "indirectkeys"`, + `name: "indirectvalues"`, `name: "chan example.com/p.Named"`, + `name: "hchan"`, + `name: "waitq"`, + `name: "sudog"`, `name: "example.com/p.Recursive"`, } { if !strings.Contains(ir, want) { @@ -192,17 +201,57 @@ func newDebugRuntimePackage() *types.Package { types.NewField(token.NoPos, pkg, "type", unsafePointer, false), types.NewField(token.NoPos, pkg, "data", unsafePointer, false), }, - "Map": { - types.NewField(token.NoPos, pkg, "count", types.Typ[types.Int], false), - }, - "Chan": { - types.NewField(token.NoPos, pkg, "count", types.Typ[types.Int], false), - }, } for name, fields := range members { obj := types.NewTypeName(token.NoPos, pkg, name, nil) types.NewNamed(obj, types.NewStruct(fields, nil), nil) pkg.Scope().Insert(obj) } + mapObj := types.NewTypeName(token.NoPos, pkg, "Map", nil) + types.NewNamed(mapObj, types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, pkg, "count", types.Typ[types.Int], false), + types.NewField(token.NoPos, pkg, "flags", types.Typ[types.Uint8], false), + types.NewField(token.NoPos, pkg, "B", types.Typ[types.Uint8], false), + types.NewField(token.NoPos, pkg, "noverflow", types.Typ[types.Uint16], false), + types.NewField(token.NoPos, pkg, "hash0", types.Typ[types.Uint32], false), + types.NewField(token.NoPos, pkg, "buckets", unsafePointer, false), + types.NewField(token.NoPos, pkg, "oldbuckets", unsafePointer, false), + types.NewField(token.NoPos, pkg, "nevacuate", types.Typ[types.Uintptr], false), + types.NewField(token.NoPos, pkg, "extra", unsafePointer, false), + }, nil), nil) + pkg.Scope().Insert(mapObj) + + waiterObj := types.NewTypeName(token.NoPos, pkg, "chanWaiter", nil) + waiter := types.NewNamed(waiterObj, nil, nil) + queueObj := types.NewTypeName(token.NoPos, pkg, "chanWaitq", nil) + queue := types.NewNamed(queueObj, nil, nil) + chanObj := types.NewTypeName(token.NoPos, pkg, "Chan", nil) + channel := types.NewNamed(chanObj, nil, nil) + waiterPtr := types.NewPointer(waiter) + waiter.SetUnderlying(types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, pkg, "prev", waiterPtr, false), + types.NewField(token.NoPos, pkg, "next", waiterPtr, false), + types.NewField(token.NoPos, pkg, "all", waiterPtr, false), + types.NewField(token.NoPos, pkg, "ch", types.NewPointer(channel), false), + types.NewField(token.NoPos, pkg, "elem", unsafePointer, false), + }, nil)) + queue.SetUnderlying(types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, pkg, "first", waiterPtr, false), + types.NewField(token.NoPos, pkg, "last", waiterPtr, false), + }, nil)) + channel.SetUnderlying(types.NewStruct([]*types.Var{ + types.NewField(token.NoPos, pkg, "qcount", types.Typ[types.Int], false), + types.NewField(token.NoPos, pkg, "dataqsiz", types.Typ[types.Int], false), + types.NewField(token.NoPos, pkg, "buf", unsafePointer, false), + types.NewField(token.NoPos, pkg, "elemsize", types.Typ[types.Int], false), + types.NewField(token.NoPos, pkg, "closed", types.Typ[types.Bool], false), + types.NewField(token.NoPos, pkg, "recvx", types.Typ[types.Int], false), + types.NewField(token.NoPos, pkg, "sendx", types.Typ[types.Int], false), + types.NewField(token.NoPos, pkg, "sendq", queue, false), + types.NewField(token.NoPos, pkg, "recvq", queue, false), + }, nil)) + pkg.Scope().Insert(waiterObj) + pkg.Scope().Insert(queueObj) + pkg.Scope().Insert(chanObj) return pkg }