Skip to content

Commit 3e1a3e1

Browse files
Jordan Romedanobi
authored andcommitted
Change map delete API
Instead of passing, what looks like a map access, to the `delete` function. Update the API to take a map as the first argument and a key as the second e.g. ``` delete(@my_map, 1); delete(@my_assoc_map, (1, 2)); ``` This change maintains support for the, now deprecated, API that accepts multiple maps.
1 parent c89a2eb commit 3e1a3e1

32 files changed

+351
-126
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ and this project adheres to
3838
- [#3249](https://github.com/bpftrace/bpftrace/pull/3249)
3939
- Faster map access for keyless maps by using BPF_MAP_TYPE_ARRAY
4040
- [#3300](https://github.com/bpftrace/bpftrace/pull/3300)
41+
- Change `delete` API to accept a map and key as separate args
42+
- [#3472](https://github.com/bpftrace/bpftrace/pull/3472)
4143
#### Deprecated
4244
#### Removed
4345
- Remove the `-dd` CLI option

docs/tutorial_one_liners.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Summarize read() bytes as a linear histogram, and traced using kernel dynamic tr
134134
# Lesson 7. Timing read()s
135135

136136
```
137-
# bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start[tid]); }'
137+
# bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start, tid); }'
138138
Attaching 2 probes...
139139
140140
[...]
@@ -168,7 +168,7 @@ Summarize the time spent in read(), in nanoseconds, as a histogram, by process n
168168
- nsecs: Nanoseconds since boot. This is a high resolution timestamp counter than can be used to time events.
169169
- /@start[tid]/: This filter checks that the start time was seen and recorded. Without this filter, this program may be launched during a read and only catch the end, resulting in a time calculation of now - zero, instead of now - start.
170170

171-
- delete(@start[tid]): this frees the variable.
171+
- delete(@start, tid): this frees the variable.
172172

173173
# Lesson 8. Count Process-Level Events
174174

docs/tutorial_one_liners_chinese.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ Attaching 1 probe...
126126
# 7. read()调用的时间
127127

128128
```
129-
# bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start[tid]); }'
129+
# bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start, tid); }'
130130
Attaching 2 probes...
131131
132132
[...]
@@ -159,7 +159,7 @@ Attaching 2 probes...
159159
- @start[tid]: 使用线程ID作为key。某一时刻,可能有许许多多的read调用正在进行,我们希望为每个调用记录一个起始时间戳。这要如何做到呢?我们可以为每个read调用建立一个唯一的标识符,并用它作为key进行统计。由于内核线程一次只能执行一个系统调用,我们可以使用线程ID作为上述标识符。
160160
- nsecs: 自系统启动到现在的纳秒数。这是一个高精度时间戳,可以用来对事件计时。
161161
- /@start[tid]/: 该过滤条件检查起始时间戳是否被记录。程序可能在某次read调用中途被启动,如果没有这个过滤条件,这个调用的时间会被统计为now-zero,而不是now-start。
162-
- delete(@start[tid]): 释放变量。
162+
- delete(@start, tid): 释放变量。
163163

164164
# 8. 统计进程级别的事件
165165

docs/tutorial_one_liners_japanese.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ read() のバイト数を線形スケールのヒストグラムとして集計
131131
# レッスン 7. read() の実行時間の測定
132132

133133
```
134-
# bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start[tid]); }'
134+
# bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start, tid); }'
135135
Attaching 2 probes...
136136
137137
[...]
@@ -165,7 +165,7 @@ read() の実行時間をナノ秒単位で計測し,プロセスごとにヒ
165165
- nsecs: マシン起動からのナノ秒を意味します.これは高精度のタイムスタンプカウンターの値で,イベント時刻の測定に利用できます.
166166
- /@start[tid]/: このフィルタは開始時間が記録されているかをチェックします.このフィルタが無い場合,このプログラムはある read の開始後に実行され,その read の終了のイベントのみを捕捉する可能性があります.この場合,結果として 現在時刻 - 開始時間ではなく,現在時刻 - 0 を計算することになります.(訳注:存在しないキーに対するマップアクセスは0を返します)
167167

168-
- delete(@start[tid]]): 変数を解放します.(訳注:delete をしたマップの値は,プログラム終了時に表示されません.)
168+
- delete(@start, tid): 変数を解放します.(訳注:delete をしたマップの値は,プログラム終了時に表示されません.)
169169

170170
# レッスン 8. プロセスレベルのイベントの計数
171171

man/adoc/bpftrace.adoc

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,15 @@ All valid identifiers can be used as `name`.
940940

941941
The data type of a variable is automatically determined during first assignment and cannot be changed afterwards.
942942

943+
==== Maps without Explicit Keys
944+
945+
Values can be assigned directly to maps without a key (sometimes refered to as scalar maps).
946+
Note: you can't iterate over these maps as they don't have an accessible key.
947+
948+
----
949+
@name = expression
950+
----
951+
943952
==== Map Keys
944953

945954
Setting single value map keys.
@@ -982,7 +991,7 @@ kprobe:do_nanosleep {
982991
983992
kretprobe:do_nanosleep /@start[tid] != 0/ {
984993
printf("slept for %d ms\n", (nsecs - @start[tid]) / 1000000);
985-
delete(@start[tid]);
994+
delete(@start, tid);
986995
}
987996
988997
/*
@@ -2356,8 +2365,8 @@ Functions that are marked *async* are asynchronous which can lead to unexpected
23562365
| Count how often this function is called.
23572366
| Sync
23582367

2359-
| <<map-functions-delete, `delete(mapkey k, ...)`>>
2360-
| Delete a single key from a map. For a single value map this deletes the only element. For a map with multiple keys, the map and the key are combined in the same argument. Multiple arguments can be passed to delete many keys at once.
2368+
| <<map-functions-delete, `delete(map m, mapkey k)`>>
2369+
| Delete a single key from a map.
23612370
| Sync
23622371

23632372
| <<map-functions-hist, `hist(int64 n[, int k])`>>
@@ -2474,23 +2483,22 @@ interval:s:10 {
24742483
=== delete
24752484

24762485
.variants
2477-
* `delete(mapkey k, ...)`
2486+
* `delete(map m, mapkey k)`
24782487

24792488
Delete a single key from a map.
2480-
For a single value map this deletes the only element.
2481-
For a map with multiple keys, the map and the key are combined in the same argument.
2482-
Multiple arguments can be passed to delete many keys at once.
2489+
For scalar maps (e.g. no explicit keys), the key is omitted and is equivalent to calling `clear`.
2490+
For map keys that are composed of multiple values (e.g. `@mymap[3, "hello"] = 1` - remember these values are represented as a tuple) the syntax would be: `delete(@mymap, (3, "hello"));`
24832491

24842492
```
24852493
kprobe:dummy {
24862494
@scalar = 1;
2495+
delete(@scalar); // ok
2496+
@single["hello"] = 1;
2497+
delete(@single, "hello"); // ok
24872498
@associative[1,2] = 1;
2488-
delete(@scalar);
2489-
delete(@associative[1,2]);
2490-
// alternatively, you can delete both at once
2491-
delete(@scalar, @associative[1,2]);
2492-
2499+
delete(@associative, (1,2)); // ok
24932500
delete(@associative); // error
2501+
delete(@associative, 1); // error
24942502
}
24952503
```
24962504

src/ast/passes/semantic_analyser.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222

2323
namespace bpftrace::ast {
2424

25+
static constexpr std::string_view DELETE_ERROR =
26+
"delete() expects a map for the first argument and a key for the second "
27+
"argument e.g. `delete(@my_map, 1);`";
28+
2529
static const std::map<std::string, std::tuple<size_t, bool>> &getIntcasts()
2630
{
2731
static const std::map<std::string, std::tuple<size_t, bool>> intcasts = {
@@ -720,10 +724,47 @@ void SemanticAnalyser::visit(Call &call)
720724
} else if (call.func == "delete") {
721725
check_assignment(call, false, false, false);
722726
if (check_varargs(call, 1, std::numeric_limits<size_t>::max())) {
723-
for (const auto *arg : call.vargs) {
724-
if (!arg->is_map)
725-
LOG(ERROR, arg->loc, err_)
726-
<< "delete() only expects maps to be provided";
727+
if (call.vargs.size() == 2) {
728+
if (!call.vargs.at(0)->is_map)
729+
LOG(ERROR, call.vargs.at(0)->loc, err_) << DELETE_ERROR;
730+
731+
auto *key_arg = call.vargs.at(1);
732+
if (!key_arg->is_map) {
733+
Map &map = static_cast<Map &>(*call.vargs.at(0));
734+
if (map.key_expr) {
735+
LOG(ERROR, call.vargs.at(0)->loc, err_)
736+
<< "delete() expects a map with no keys for the first argument";
737+
} else {
738+
auto *mapkey = get_map_key_type(map);
739+
if (mapkey) {
740+
auto key_type = create_key_type(call.vargs.at(1)->type,
741+
call.vargs.at(1)->loc);
742+
if (!mapkey->IsSameType(key_type) ||
743+
!key_type.FitsInto(*mapkey)) {
744+
LOG(ERROR, call.vargs.at(1)->loc, err_)
745+
<< "Argument mismatch for " << map.ident << ": "
746+
<< "trying to delete with key of type: '"
747+
<< call.vargs.at(1)->type << "' when map has key of type: '"
748+
<< *mapkey << "'";
749+
}
750+
}
751+
}
752+
753+
// We're modifying the AST here to support the deprecated delete
754+
// API so subsequent passes will fall through to the else statement
755+
// below. Once we remove the old API, we can handle this properly.
756+
map.key_expr = call.vargs.at(1);
757+
call.vargs.pop_back();
758+
}
759+
} else {
760+
// This supports the deprecated delete API of passing multiple maps as
761+
// args
762+
for (const auto *arg : call.vargs) {
763+
if (!arg->is_map) {
764+
LOG(ERROR, arg->loc, err_) << DELETE_ERROR;
765+
break;
766+
}
767+
}
727768
}
728769
}
729770
call.type = CreateNone();

tests/codegen/call_delete.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ namespace codegen {
66

77
TEST(codegen, call_delete)
88
{
9-
test("kprobe:f { @x = 1; delete(@x) }",
9+
test("kprobe:f { @x[1] = 1; delete(@x, 1) }",
10+
11+
NAME);
12+
}
13+
14+
TEST(codegen, call_delete_deprecated)
15+
{
16+
test("kprobe:f { @x[1] = 1; delete(@x[1]) }",
1017

1118
NAME);
1219
}

tests/codegen/llvm/call_delete.ll

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,27 @@ target triple = "bpf-pc-linux"
99

1010
@LICENSE = global [4 x i8] c"GPL\00", section "license"
1111
@AT_x = dso_local global %"struct map_t" zeroinitializer, section ".maps", !dbg !0
12-
@ringbuf = dso_local global %"struct map_t.0" zeroinitializer, section ".maps", !dbg !22
13-
@event_loss_counter = dso_local global %"struct map_t.1" zeroinitializer, section ".maps", !dbg !36
12+
@ringbuf = dso_local global %"struct map_t.0" zeroinitializer, section ".maps", !dbg !20
13+
@event_loss_counter = dso_local global %"struct map_t.1" zeroinitializer, section ".maps", !dbg !34
1414

1515
; Function Attrs: nounwind
1616
declare i64 @llvm.bpf.pseudo(i64 %0, i64 %1) #0
1717

18-
define i64 @kprobe_f_1(ptr %0) section "s_kprobe_f_1" !dbg !41 {
18+
define i64 @kprobe_f_1(ptr %0) section "s_kprobe_f_1" !dbg !50 {
1919
entry:
20-
%"@x_zero" = alloca i64, align 8
2120
%"@x_key1" = alloca i64, align 8
2221
%"@x_val" = alloca i64, align 8
2322
%"@x_key" = alloca i64, align 8
2423
call void @llvm.lifetime.start.p0(i64 -1, ptr %"@x_key")
25-
store i64 0, ptr %"@x_key", align 8
24+
store i64 1, ptr %"@x_key", align 8
2625
call void @llvm.lifetime.start.p0(i64 -1, ptr %"@x_val")
2726
store i64 1, ptr %"@x_val", align 8
2827
%update_elem = call i64 inttoptr (i64 2 to ptr)(ptr @AT_x, ptr %"@x_key", ptr %"@x_val", i64 0)
2928
call void @llvm.lifetime.end.p0(i64 -1, ptr %"@x_val")
3029
call void @llvm.lifetime.end.p0(i64 -1, ptr %"@x_key")
3130
call void @llvm.lifetime.start.p0(i64 -1, ptr %"@x_key1")
32-
store i64 0, ptr %"@x_key1", align 8
33-
call void @llvm.lifetime.start.p0(i64 -1, ptr %"@x_zero")
34-
store i64 0, ptr %"@x_zero", align 8
35-
%update_elem2 = call i64 inttoptr (i64 2 to ptr)(ptr @AT_x, ptr %"@x_key1", ptr %"@x_zero", i64 0)
36-
call void @llvm.lifetime.end.p0(i64 -1, ptr %"@x_zero")
31+
store i64 1, ptr %"@x_key1", align 8
32+
%delete_elem = call i64 inttoptr (i64 3 to ptr)(ptr @AT_x, ptr %"@x_key1")
3733
call void @llvm.lifetime.end.p0(i64 -1, ptr %"@x_key1")
3834
ret i64 0
3935
}
@@ -47,8 +43,8 @@ declare void @llvm.lifetime.end.p0(i64 immarg %0, ptr nocapture %1) #1
4743
attributes #0 = { nounwind }
4844
attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
4945

50-
!llvm.dbg.cu = !{!38}
51-
!llvm.module.flags = !{!40}
46+
!llvm.dbg.cu = !{!47}
47+
!llvm.module.flags = !{!49}
5248

5349
!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
5450
!1 = distinct !DIGlobalVariable(name: "AT_x", linkageName: "global", scope: !2, file: !2, type: !3, isLocal: false, isDefinition: true)
@@ -57,44 +53,53 @@ attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: re
5753
!4 = !{!5, !11, !16, !19}
5854
!5 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !2, file: !2, baseType: !6, size: 64)
5955
!6 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64)
60-
!7 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 64, elements: !9)
56+
!7 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 32, elements: !9)
6157
!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
6258
!9 = !{!10}
63-
!10 = !DISubrange(count: 2, lowerBound: 0)
59+
!10 = !DISubrange(count: 1, lowerBound: 0)
6460
!11 = !DIDerivedType(tag: DW_TAG_member, name: "max_entries", scope: !2, file: !2, baseType: !12, size: 64, offset: 64)
6561
!12 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !13, size: 64)
66-
!13 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 32, elements: !14)
62+
!13 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 131072, elements: !14)
6763
!14 = !{!15}
68-
!15 = !DISubrange(count: 1, lowerBound: 0)
64+
!15 = !DISubrange(count: 4096, lowerBound: 0)
6965
!16 = !DIDerivedType(tag: DW_TAG_member, name: "key", scope: !2, file: !2, baseType: !17, size: 64, offset: 128)
7066
!17 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !18, size: 64)
71-
!18 = !DIBasicType(name: "int32", size: 32, encoding: DW_ATE_signed)
72-
!19 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !2, file: !2, baseType: !20, size: 64, offset: 192)
73-
!20 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !21, size: 64)
74-
!21 = !DIBasicType(name: "int64", size: 64, encoding: DW_ATE_signed)
75-
!22 = !DIGlobalVariableExpression(var: !23, expr: !DIExpression())
76-
!23 = distinct !DIGlobalVariable(name: "ringbuf", linkageName: "global", scope: !2, file: !2, type: !24, isLocal: false, isDefinition: true)
77-
!24 = !DICompositeType(tag: DW_TAG_structure_type, scope: !2, file: !2, size: 128, elements: !25)
78-
!25 = !{!26, !31}
79-
!26 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !2, file: !2, baseType: !27, size: 64)
80-
!27 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !28, size: 64)
81-
!28 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 864, elements: !29)
82-
!29 = !{!30}
83-
!30 = !DISubrange(count: 27, lowerBound: 0)
84-
!31 = !DIDerivedType(tag: DW_TAG_member, name: "max_entries", scope: !2, file: !2, baseType: !32, size: 64, offset: 64)
85-
!32 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !33, size: 64)
86-
!33 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 8388608, elements: !34)
87-
!34 = !{!35}
88-
!35 = !DISubrange(count: 262144, lowerBound: 0)
89-
!36 = !DIGlobalVariableExpression(var: !37, expr: !DIExpression())
90-
!37 = distinct !DIGlobalVariable(name: "event_loss_counter", linkageName: "global", scope: !2, file: !2, type: !3, isLocal: false, isDefinition: true)
91-
!38 = distinct !DICompileUnit(language: DW_LANG_C, file: !2, producer: "bpftrace", isOptimized: false, runtimeVersion: 0, emissionKind: LineTablesOnly, globals: !39)
92-
!39 = !{!0, !22, !36}
93-
!40 = !{i32 2, !"Debug Info Version", i32 3}
94-
!41 = distinct !DISubprogram(name: "kprobe_f_1", linkageName: "kprobe_f_1", scope: !2, file: !2, type: !42, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !38, retainedNodes: !46)
95-
!42 = !DISubroutineType(types: !43)
96-
!43 = !{!21, !44}
97-
!44 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !45, size: 64)
98-
!45 = !DIBasicType(name: "int8", size: 8, encoding: DW_ATE_signed)
99-
!46 = !{!47}
100-
!47 = !DILocalVariable(name: "ctx", arg: 1, scope: !41, file: !2, type: !44)
67+
!18 = !DIBasicType(name: "int64", size: 64, encoding: DW_ATE_signed)
68+
!19 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !2, file: !2, baseType: !17, size: 64, offset: 192)
69+
!20 = !DIGlobalVariableExpression(var: !21, expr: !DIExpression())
70+
!21 = distinct !DIGlobalVariable(name: "ringbuf", linkageName: "global", scope: !2, file: !2, type: !22, isLocal: false, isDefinition: true)
71+
!22 = !DICompositeType(tag: DW_TAG_structure_type, scope: !2, file: !2, size: 128, elements: !23)
72+
!23 = !{!24, !29}
73+
!24 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !2, file: !2, baseType: !25, size: 64)
74+
!25 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !26, size: 64)
75+
!26 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 864, elements: !27)
76+
!27 = !{!28}
77+
!28 = !DISubrange(count: 27, lowerBound: 0)
78+
!29 = !DIDerivedType(tag: DW_TAG_member, name: "max_entries", scope: !2, file: !2, baseType: !30, size: 64, offset: 64)
79+
!30 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !31, size: 64)
80+
!31 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 8388608, elements: !32)
81+
!32 = !{!33}
82+
!33 = !DISubrange(count: 262144, lowerBound: 0)
83+
!34 = !DIGlobalVariableExpression(var: !35, expr: !DIExpression())
84+
!35 = distinct !DIGlobalVariable(name: "event_loss_counter", linkageName: "global", scope: !2, file: !2, type: !36, isLocal: false, isDefinition: true)
85+
!36 = !DICompositeType(tag: DW_TAG_structure_type, scope: !2, file: !2, size: 256, elements: !37)
86+
!37 = !{!38, !43, !44, !19}
87+
!38 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !2, file: !2, baseType: !39, size: 64)
88+
!39 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !40, size: 64)
89+
!40 = !DICompositeType(tag: DW_TAG_array_type, baseType: !8, size: 64, elements: !41)
90+
!41 = !{!42}
91+
!42 = !DISubrange(count: 2, lowerBound: 0)
92+
!43 = !DIDerivedType(tag: DW_TAG_member, name: "max_entries", scope: !2, file: !2, baseType: !6, size: 64, offset: 64)
93+
!44 = !DIDerivedType(tag: DW_TAG_member, name: "key", scope: !2, file: !2, baseType: !45, size: 64, offset: 128)
94+
!45 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !46, size: 64)
95+
!46 = !DIBasicType(name: "int32", size: 32, encoding: DW_ATE_signed)
96+
!47 = distinct !DICompileUnit(language: DW_LANG_C, file: !2, producer: "bpftrace", isOptimized: false, runtimeVersion: 0, emissionKind: LineTablesOnly, globals: !48)
97+
!48 = !{!0, !20, !34}
98+
!49 = !{i32 2, !"Debug Info Version", i32 3}
99+
!50 = distinct !DISubprogram(name: "kprobe_f_1", linkageName: "kprobe_f_1", scope: !2, file: !2, type: !51, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !47, retainedNodes: !55)
100+
!51 = !DISubroutineType(types: !52)
101+
!52 = !{!18, !53}
102+
!53 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !54, size: 64)
103+
!54 = !DIBasicType(name: "int8", size: 8, encoding: DW_ATE_signed)
104+
!55 = !{!56}
105+
!56 = !DILocalVariable(name: "ctx", arg: 1, scope: !50, file: !2, type: !53)

0 commit comments

Comments
 (0)