From c8d3794dbc9a81bc2f0bb1fb653a454ac91ee3b8 Mon Sep 17 00:00:00 2001 From: fche Date: Thu, 8 Nov 2001 21:55:10 +0000 Subject: [PATCH] * call graph profiling support [sid/bsp] 2001-11-08 Frank Ch. Eigler * configrun-sid.in (gprof): Connect call-graph pins. [sid/include] 2001-11-08 Frank Ch. Eigler * sidcpuutil.h (basic_cpu): Add cg caller/callee pins, trace_stream fields. (basic_cpu ctor): Initialize them. (cg_profile): New function. [sid/component/cgen-cpu] 2001-11-08 Frank Ch. Eigler * cgen-cpu.h (cgen_bi_endian_cpu): Remove trace_stream field: it's in basic_cpu now. * compCGEN.cxx: Corresponding change. [sid/component/profiling] 2001-11-08 Frank Ch. Eigler * gprof.cxx (gprof_component): Add cg caller/callee pins, cg_count_map field. (accumulate_call): New function. (reset): Clear cg map. (store): Emit call graph info. Emit histogram iff nonempty. * sw-profile-gprof.xml: Document call graph functionality. * sw-profile-gprof.txt: Regenerated. --- sid/bsp/ChangeLog | 4 + sid/bsp/configrun-sid.in | 6 +- sid/component/cgen-cpu/ChangeLog | 6 + sid/component/cgen-cpu/cgen-cpu.h | 2 - sid/component/cgen-cpu/compCGEN.cxx | 3 +- sid/component/profiling/ChangeLog | 10 ++ sid/component/profiling/gprof.cxx | 167 +++++++++++++++++++-------- sid/component/profiling/sw-profile-gprof.txt | 78 ++++++++----- sid/component/profiling/sw-profile-gprof.xml | 47 +++++--- sid/include/ChangeLog | 7 ++ sid/include/sidcpuutil.h | 23 ++++ 11 files changed, 249 insertions(+), 104 deletions(-) diff --git a/sid/bsp/ChangeLog b/sid/bsp/ChangeLog index 7a76bd941f..74ae9ed659 100644 --- a/sid/bsp/ChangeLog +++ b/sid/bsp/ChangeLog @@ -1,3 +1,7 @@ +2001-11-08 Frank Ch. Eigler + + * configrun-sid.in (gprof): Connect call-graph pins. + 2001-10-29 Frank Ch. Eigler * configrun-sid.in (memory-region): Create component in first section. diff --git a/sid/bsp/configrun-sid.in b/sid/bsp/configrun-sid.in index 6768e07b79..425a42c22f 100644 --- a/sid/bsp/configrun-sid.in +++ b/sid/bsp/configrun-sid.in @@ -100,7 +100,7 @@ $load_mapper_data = "cpu-mapper access-port"; "enable-warnings!" => ["enable-warnings", "Enable CPU execution warnings.", "no"], "memory-region=s@" => ["memory-region=BASE,SIZE", "Add RAM region from BASE to BASE+SIZE-1.", "no", "other options:", - " bus=MAPPER Attach memory to bus MAPPER, not data", + " bus=MAPPER Attach memory to bus MAPPER", " read-only Make memory read-only", " alias=BASE2 Add an alias at BASE2", " file=FILENAME Load/save memory image from file", @@ -120,7 +120,7 @@ $load_mapper_data = "cpu-mapper access-port"; "persistent!" => ["persistent", "Rerun top-level loop indefinitely.", "no"], "no-run!" => ["no-run", "Make config file (--save-temps) and exit.", "no"], "insn-count=i" => ["insn-count=N", "Block of uninterrupted ticks for insns", "10000"], - "gprof!" => ["gprof", "GPROF-profile, collect every N ticks", "no"], + "gprof!" => ["gprof", "GPROF-profile, collect every insn-count ticks", "no"], "sidrtc=s" => [], "sidcodec=s" => [], "wrap=s@" => ["wrap=COMPONENT", "Turn on SID API tracing for named component", "none"], @@ -462,6 +462,8 @@ if ($opt_gprof) connect-pin target-sched 0-event -> gprof sample connect-pin deinit-sequence output-7 -> gprof store relate gprof target-component cpu +connect-pin cpu cg-caller -> gprof cg-caller +connect-pin cpu cg-callee -> gprof cg-callee set gprof value-attribute pc set gprof bucket-size 4 # bytes per bucket "; diff --git a/sid/component/cgen-cpu/ChangeLog b/sid/component/cgen-cpu/ChangeLog index ca2c30e45f..54d2faf12c 100644 --- a/sid/component/cgen-cpu/ChangeLog +++ b/sid/component/cgen-cpu/ChangeLog @@ -1,3 +1,9 @@ +2001-11-08 Frank Ch. Eigler + + * cgen-cpu.h (cgen_bi_endian_cpu): Remove trace_stream field: it's + in basic_cpu now. + * compCGEN.cxx: Corresponding change. + 2001-08-03 matthew green * cgen-cpu.h (~cgen_bi_endian_cpu): Add throw() specifier. diff --git a/sid/component/cgen-cpu/cgen-cpu.h b/sid/component/cgen-cpu/cgen-cpu.h index 09cb07a5f5..8490cac187 100644 --- a/sid/component/cgen-cpu/cgen-cpu.h +++ b/sid/component/cgen-cpu/cgen-cpu.h @@ -62,8 +62,6 @@ public: bool warnings_enabled; // Print messages here. - // XXX: Should be a pointer or somesuch, so it can be changed during a run. - ostream& trace_stream; // Cover fns to start/end insn tracing. void begin_trace (PCADDR pc, const char* insn_name); void end_trace (); diff --git a/sid/component/cgen-cpu/compCGEN.cxx b/sid/component/cgen-cpu/compCGEN.cxx index f4622b0467..b655447761 100644 --- a/sid/component/cgen-cpu/compCGEN.cxx +++ b/sid/component/cgen-cpu/compCGEN.cxx @@ -34,8 +34,7 @@ using namespace cgen; // ---------------------------------------------------------------------------- -cgen_bi_endian_cpu::cgen_bi_endian_cpu (): - trace_stream (cout) +cgen_bi_endian_cpu::cgen_bi_endian_cpu () { trace_count = 0; warnings_enabled = false; diff --git a/sid/component/profiling/ChangeLog b/sid/component/profiling/ChangeLog index a97e5065f7..18b3dd994a 100644 --- a/sid/component/profiling/ChangeLog +++ b/sid/component/profiling/ChangeLog @@ -1,3 +1,13 @@ +2001-11-08 Frank Ch. Eigler + + * gprof.cxx (gprof_component): Add cg caller/callee pins, cg_count_map + field. + (accumulate_call): New function. + (reset): Clear cg map. + (store): Emit call graph info. Emit histogram iff nonempty. + * sw-profile-gprof.xml: Document call graph functionality. + * sw-profile-gprof.txt: Regenerated. + 2001-08-03 matthew green * gprof.cxx (~gprof_component): Add prototype. diff --git a/sid/component/profiling/gprof.cxx b/sid/component/profiling/gprof.cxx index ff69cdcd3b..16bb1635c8 100644 --- a/sid/component/profiling/gprof.cxx +++ b/sid/component/profiling/gprof.cxx @@ -1,6 +1,6 @@ // gprof.cxx - A component for generating gprof profile data. -*- C++ -*- -// Copyright (C) 1999, 2000 Red Hat. +// Copyright (C) 1999-2001 Red Hat. // This file is part of SID and is licensed under the GPL. // See the file COPYING.SID for conditions for redistribution. @@ -58,6 +58,7 @@ namespace profiling_components using sidutil::no_bus_component; using sidutil::no_accessor_component; using sidutil::fixed_relation_map_component; + using sidutil::input_pin; using sidutil::callback_pin; using sidutil::make_attribute; using sidutil::parse_attribute; @@ -74,8 +75,10 @@ namespace profiling_components using std::ofstream; using std::cerr; using std::endl; + using std::pair; using std::ostream; + // ---------------------------------------------------------------------------- class gprof_component: public virtual component, @@ -86,13 +89,24 @@ namespace profiling_components protected no_bus_component { #ifdef HAVE_HASHING + struct hash_cg_pair + { + size_t operator () (const pair& s) const + { + return (s.first << 1) ^ s.second; + } + }; + typedef hash_map hitcount_map_t; + typedef hash_map,host_int_4,hash_cg_pair> cg_count_map_t; #else typedef map hitcount_map_t; + typedef map,host_int_4> cg_count_map_t; #endif // statistics hitcount_map_t value_hitcount_map; + cg_count_map_t cg_count_map; host_int_4 value_count; host_int_4 value_min, value_max; host_int_4 limit_min, limit_max; @@ -105,10 +119,13 @@ namespace profiling_components endian output_file_format; callback_pin accumulate_pin; + + input_pin cg_caller_pin; + callback_pin cg_callee_pin; + callback_pin reset_pin; callback_pin store_pin; - string bucket_size_get() { return make_attribute (this->bucket_size); @@ -164,8 +181,32 @@ namespace profiling_components this->value_hitcount_map [quantized] ++; } + void accumulate_call (host_int_4 selfpc) + { + host_int_4 callerpc = this->cg_caller_pin.sense(); + + // Reject out-of-bounds samples + if (selfpc < this->limit_min || selfpc > this->limit_max) return; + if (callerpc < this->limit_min || callerpc > this->limit_max) return; + + value_count ++; + + assert (this->bucket_size != 0); + host_int_4 c_quantized = (callerpc / this->bucket_size) * this->bucket_size; + host_int_4 s_quantized = (selfpc / this->bucket_size) * this->bucket_size; + + if (c_quantized < this->value_min) this->value_min = c_quantized; + if (s_quantized < this->value_min) this->value_min = s_quantized; + if (c_quantized > this->value_max) this->value_max = c_quantized; + if (s_quantized > this->value_max) this->value_max = s_quantized; + + this->cg_count_map [make_pair(c_quantized,s_quantized)] ++; + } + + void reset (host_int_4) { + this->cg_count_map.clear (); this->value_hitcount_map.clear (); this->value_min = ~0; this->value_max = 0; @@ -244,61 +285,80 @@ namespace profiling_components put_bytes (of, host_int_4(0), 4); // gmon_hdr.spare put_bytes (of, host_int_4(0), 4); // gmon_hdr.spare - // We may have to loop and dump out several adjacent histogram - // tables, because histogram bucket count overflow. The - // bucket counts are limited to 16 bits, but a 32-bit counter - // in gprof may be accumulated my multiple overlapping - // histogram tables. We copy the histogram table here, since - // its counters will be decremented by up to 2**16-1 per - // iteration. - hitcount_map_t value_hitcount_map_copy = this->value_hitcount_map; - while (true) + if (! this->value_hitcount_map.empty()) { - // write a new histogram record - // GMON_Record_Tag - put_bytes (of, host_int_1(0), 1); // GMON_TAG_TIME_HIST - // gmon_hist_hdr - put_bytes (of, this->value_min, 4); // gmon_hist_hdr.low_pc - put_bytes (of, this->value_max, 4); // gmon_hist_hdr.high_pc - assert (this->bucket_size != 0); - host_int_4 num_buckets = 1 + (this->value_max - this->value_min) / this->bucket_size; - put_bytes (of, num_buckets, 4); // gmon_hist_hdr.hist_size - // XXX: actual prof_rate not available here ... - put_bytes (of, host_int_4(1), 4); // gmon_hist_hdr.prof_rate - put_bytes (of, "tick", 15); // gmon_hist_hdr.dimen - put_bytes (of, "t", 1); // gmon_hist_hdr.dimen_abbrev - - // Dump out histogram counts - bool overflow = false; - for (host_int_4 bucket = this->value_min; - bucket <= this->value_max; - bucket += this->bucket_size) + // We may have to loop and dump out several adjacent histogram + // tables, because histogram bucket count overflow. The + // bucket counts are limited to 16 bits, but a 32-bit counter + // in gprof may be accumulated my multiple overlapping + // histogram tables. We copy the histogram table here, since + // its counters will be decremented by up to 2**16-1 per + // iteration. + hitcount_map_t value_hitcount_map_copy = this->value_hitcount_map; + while (true) { - const host_int_4 max_count = 65535; - host_int_4 count = 0; - - // Check if this bucket exists by find() instead of a - // blind []-lookup, because the latter would allocate - // fresh & useless 0-count buckets for all non-touched - // values. - hitcount_map_t::iterator b = value_hitcount_map_copy.find (bucket); - if (b != value_hitcount_map_copy.end()) - count = b->second; - - if (count > max_count) // overflow! + // write a new histogram record + // GMON_Record_Tag + put_bytes (of, host_int_1(0), 1); // GMON_TAG_TIME_HIST + // gmon_hist_hdr + put_bytes (of, this->value_min, 4); // gmon_hist_hdr.low_pc + put_bytes (of, this->value_max, 4); // gmon_hist_hdr.high_pc + assert (this->bucket_size != 0); + host_int_4 num_buckets = 1 + (this->value_max - this->value_min) / this->bucket_size; + put_bytes (of, num_buckets, 4); // gmon_hist_hdr.hist_size + // XXX: actual prof_rate not available here ... + put_bytes (of, host_int_4(1), 4); // gmon_hist_hdr.prof_rate + put_bytes (of, "tick", 15); // gmon_hist_hdr.dimen + put_bytes (of, "t", 1); // gmon_hist_hdr.dimen_abbrev + + // Dump out histogram counts + bool overflow = false; + for (host_int_4 bucket = this->value_min; + bucket <= this->value_max; + bucket += this->bucket_size) { - put_bytes (of, host_int_2(max_count), 2); - b->second -= max_count; - overflow = true; - } - else - { - put_bytes (of, host_int_2(count), 2); + const host_int_4 max_count = 65535; + host_int_4 count = 0; + + // Check if this bucket exists by find() instead of a + // blind []-lookup, because the latter would allocate + // fresh & useless 0-count buckets for all non-touched + // values. + hitcount_map_t::iterator b = value_hitcount_map_copy.find (bucket); + if (b != value_hitcount_map_copy.end()) + count = b->second; + + if (count > max_count) // overflow! + { + put_bytes (of, host_int_2(max_count), 2); + b->second -= max_count; + overflow = true; + } + else + { + put_bytes (of, host_int_2(count), 2); + } } + + if (!overflow) + break; } + } // (emitting hash table?) - if (!overflow) - break; + // Now spit out the call graph stastics. + cg_count_map_t::const_iterator ci = this->cg_count_map.begin(); + while (ci != this->cg_count_map.end()) + { + // write a new histogram record + // GMON_Record_Tag + put_bytes (of, host_int_1(1), 1); // GMON_TAG_CG_ARC + + // gmon_hist_hdr + put_bytes (of, ci->first.first, 4); // cg caller + put_bytes (of, ci->first.second, 4); // cg self + put_bytes (of, ci->second, 4); // cg count + + ci ++; } of.close (); @@ -318,11 +378,16 @@ namespace profiling_components output_file ("gmon.out"), output_file_format (endian_unknown), accumulate_pin (this, & gprof_component::accumulate), + cg_callee_pin (this, & gprof_component::accumulate_call), reset_pin (this, & gprof_component::reset), store_pin (this, & gprof_component::store) { add_pin ("sample", & this->accumulate_pin); add_attribute ("sample", & this->accumulate_pin, "pin"); + add_pin ("cg-caller", & this->cg_caller_pin); + add_attribute ("cg-caller", & this->cg_caller_pin, "pin"); + add_pin ("cg-callee", & this->cg_callee_pin); + add_attribute ("cg-callee", & this->cg_callee_pin, "pin"); add_pin ("reset", & this->reset_pin); add_attribute ("reset", & this->reset_pin, "pin"); add_pin ("store", & this->store_pin); diff --git a/sid/component/profiling/sw-profile-gprof.txt b/sid/component/profiling/sw-profile-gprof.txt index 9ade39b1c5..b63ad043db 100644 --- a/sid/component/profiling/sw-profile-gprof.txt +++ b/sid/component/profiling/sw-profile-gprof.txt @@ -54,21 +54,31 @@ Functionality: | | histogram, in the appropriate | | | bucket. The bucket is chosen | | | by masking the number into | - | | bucket-size-wide buckets. | + | | bucket-size-wide buckets. If | + | | the target component is unset, | + | | or its target attribute does | + | | not result in a valid numeric | + | | string, no sample is | + | | accumulated. | | | | - | | If the target component is | - | | unset, or its target attribute | - | | does not result in a valid | - | | numeric string, no sample is | - | | accumulated. If a sample was | - | | collected, and falls between | - | | the limit-min and limit-max | - | | attributes' bounds, | - | | value-count is incremented, | - | | and value-min and value-max | - | | registers are updated. | + | | Alternately, if the cg-caller | + | | and then the cg-callee pins | + | | are driven, the values driven | + | | are interpreted as the caller | + | | and the callee PC addresses of | + | | a subroutine call event. These | + | | events are recorded in a | + | | dynamic call graph. | | | | - | | Whenever the reset pin is | + | | If a sample was collected, and | + | | falls between the limit-min | + | | and limit-max attributes' | + | | bounds, value-count is | + | | incremented, and value-min and | + | | value-max registers are | + | | updated. | + |----------------+--------------------------------| + | resetting | Whenever the reset pin is | | | driven, the entire accumulated | | | histogram and associated | | | counters are discarded and a | @@ -79,18 +89,19 @@ Functionality: | profile | Whenever the store pin is | | generation | driven, this component dumps | | | the entire accumulated | - | | histogram into a gprof | - | | (version 1) formatted file. | - | | The file's endianness is | + | | histogram and call graph into | + | | a gprof (version 1) formatted | + | | file. The file's endianness is | | | chosen based on the | | | target-file-endianness | | | setting. If it is unset, the | | | target component's endian | | | attribute will be queried and | | | used if valid. The histogram | - | | is not automatically cleared | - | | after the store operation. | - | | This makes it possible to save | + | | and call graph are not | + | | automatically cleared after | + | | the store operation. This | + | | makes it possible to save | | | multiple generational | | | profiling files to track | | | history of a program | @@ -161,16 +172,18 @@ Component Reference: +-------------------------------------------------+ | pins | |-------------------------------------------------| - | name | direction | legalvalues | behaviors | - |--------+-----------+-------------+--------------| - | sample | in | any | data | - | | | | gathering | - |--------+-----------+-------------+--------------| - | reset | in | any | data | - | | | | gathering | - |--------+-----------+-------------+--------------| - | store | in | any | profile | - | | | | generation | + | name |direction|legalvalues| behaviors | + |---------+---------+-----------+-----------------| + |reset |in |any |resetting | + |---------+---------+-----------+-----------------| + |sample |in |any |data gathering | + |---------+---------+-----------+-----------------| + |cg-caller|in |any |data gathering | + |---------+---------+-----------+-----------------| + |cg-callee|in |any |data gathering | + |---------+---------+-----------+-----------------| + |store |in |any |profile | + | | | |generation | +-------------------------------------------------+ +----------------------------------------------------------------------------------+ @@ -201,10 +214,15 @@ Component Reference: |----------------------+--------+------------------------+----------+-------------|| |output-file-endianness|setting |0/1/2/little/big/unknown|unknown |configuration|| |----------------------+--------+------------------------+----------+-------------|| +|reset |pin |- |- |resetting || +|----------------------+--------+------------------------+----------+-------------|| |sample |pin |- |- |data || | | | | |gathering || |----------------------+--------+------------------------+----------+-------------|| -|reset |pin |- |- |data || +|cg-caller |pin |- |- |data || +| | | | |gathering || +|----------------------+--------+------------------------+----------+-------------|| +|cg-callee |pin |- |- |data || | | | | |gathering || |----------------------+--------+------------------------+----------+-------------|| |store |pin |- |- |profile || diff --git a/sid/component/profiling/sw-profile-gprof.xml b/sid/component/profiling/sw-profile-gprof.xml index 31a14d137e..99bff61cd3 100644 --- a/sid/component/profiling/sw-profile-gprof.xml +++ b/sid/component/profiling/sw-profile-gprof.xml @@ -5,11 +5,12 @@ + - + + - @@ -19,8 +20,10 @@ + - + + @@ -63,20 +66,31 @@

- Whenever the sample pin is driven, this component takes a single - sample of the configured target component's value attribute. The - attribute is interpreted as a numeric string, and the resulting - number is accumulated in a big histogram, in the appropriate bucket. - The bucket is chosen by masking the number into bucket-size-wide - buckets. + Whenever the sample pin is driven, this component + takes a single sample of the configured target component's value + attribute. The attribute is interpreted as a numeric string, + and the resulting number is accumulated in a big histogram, in + the appropriate bucket. The bucket is chosen by masking the + number into bucket-size-wide buckets. + If the target component is unset, or its target attribute does + not result in a valid numeric string, no sample is accumulated. +

+

Alternately, if the cg-caller and then the cg-callee + pins are driven, the values driven are interpreted as the caller and the + callee PC addresses of a subroutine call event. These events are recorded + in a dynamic call graph.

- If the target component is unset, or its target attribute does not - result in a valid numeric string, no sample is accumulated. If a - sample was collected, and falls between the limit-min and limit-max - attributes' bounds, value-count is incremented, and value-min - and value-max registers are updated. + If a sample was collected, and falls between the + limit-min and + limit-max attributes' bounds, + value-count is incremented, and + value-min and + value-max registers are updated.

+
+ +

Whenever the reset pin is driven, the entire accumulated histogram and associated counters are discarded and a new count @@ -87,19 +101,18 @@ Whenever the store pin is driven, this component dumps the entire - accumulated histogram into a gprof (version 1) formatted file. The + accumulated histogram and call graph into a gprof (version 1) formatted file. The file's endianness is chosen based on the target-file-endianness setting. If it is unset, the target component's endian attribute will be queried and used if valid. - The histogram is not automatically cleared after the store + The histogram and call graph are not automatically cleared after the store operation. This makes it possible to save multiple generational profiling files to track history of a program cumulatively. If the reset pin is triggered after every store, then separate intervals of the program's execution may be profiled. - diff --git a/sid/include/ChangeLog b/sid/include/ChangeLog index dc13e5ba70..0d3c7ce4e9 100644 --- a/sid/include/ChangeLog +++ b/sid/include/ChangeLog @@ -1,3 +1,10 @@ +2001-11-08 Frank Ch. Eigler + + * sidcpuutil.h (basic_cpu): Add cg caller/callee pins, trace_stream + fields. + (basic_cpu ctor): Initialize them. + (cg_profile): New function. + 2001-10-04 Frank Ch. Eigler * sidcpuutil.h (basic_cpu ctor): Initialize those tracing flags. diff --git a/sid/include/sidcpuutil.h b/sid/include/sidcpuutil.h index e5dfce47e4..5a93d8dd22 100644 --- a/sid/include/sidcpuutil.h +++ b/sid/include/sidcpuutil.h @@ -200,6 +200,8 @@ namespace sidutil mutable sid::host_int_8 total_latency; sid::host_int_4 current_step_insn_count; output_pin step_cycles_pin; + output_pin cg_caller_pin; + output_pin cg_callee_pin; public: bool trace_extract_p; bool trace_result_p; @@ -207,6 +209,7 @@ namespace sidutil bool trace_semantics_p; bool trace_counter_p; bool enable_step_trap_p; + std::ostream& trace_stream; void step_pin_handler (sid::host_int_4) { @@ -250,6 +253,23 @@ namespace sidutil { this->step_cycles_pin.drive (n); } + void cg_profile (sid::host_int_4 caller, sid::host_int_4 callee) + { + // The drive sequence is important: see sw-profile-gprof + this->cg_caller_pin.drive (caller); + this->cg_callee_pin.drive (callee); + + if (UNLIKELY(this->trace_result_p)) + { + this->trace_stream << "cg-profile " + << make_numeric_attribute (caller, + std::ios::hex|std::ios::showbase) + << "->" + << make_numeric_attribute (callee, + std::ios::hex|std::ios::showbase) + << " "; + } + } protected: virtual sid::host_int_8 latency_to_cycles (sid::host_int_8 num) @@ -442,6 +462,7 @@ public: triggerpoint_manager (this), step_pin (this, & basic_cpu::step_pin_handler), yield_pin (this, & basic_cpu::yield_pin_handler), + trace_stream (std::cout), reset_pin (this, & basic_cpu::reset_pin_handler), flush_icache_pin (this, & basic_cpu::flush_icache_pin_handler), pc_set_pin (this, & basic_cpu::pc_set_pin_handler), @@ -462,6 +483,8 @@ public: add_pin ("reset!", & this->reset_pin); add_pin ("yield", & this->yield_pin); add_pin ("start-pc-set!", & this->pc_set_pin); + add_pin ("cg-caller", & this->cg_caller_pin); + add_pin ("cg-callee", & this->cg_callee_pin); add_pin ("endian-set!", & this->endian_set_pin); add_watchable_pin ("trap", & this->trap_type_pin); // output side add_watchable_pin ("trap-code", & this->trap_code_pin); -- 2.11.0