Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
adfec61
Add scatter-gather parsing to libarchfpga
AmirhosseinPoolad Jul 21, 2025
fdbbfae
Add initial documentation
AmirhosseinPoolad Jul 21, 2025
95abde3
Fix issues with scatter-gather parsing
AmirhosseinPoolad Jul 22, 2025
cb36d65
Fix more scatter-gather issues and add documentation
AmirhosseinPoolad Jul 24, 2025
dbd63ce
Add scatter/gather switchpoint sanitization
AmirhosseinPoolad Jul 24, 2025
15b2824
Add side parsing to <wireconn> tag
AmirhosseinPoolad Jul 24, 2025
9c6cc76
Add <sg_link_list> tag
AmirhosseinPoolad Jul 24, 2025
4fe5dfa
Add sphinx documentation for scatter-gather patterns
AmirhosseinPoolad Jul 24, 2025
396e848
Fix Formatting
AmirhosseinPoolad Jul 24, 2025
3426204
Fix scatter-gather tag being mandatory
AmirhosseinPoolad Jul 25, 2025
b1eb6fc
Fix wireconn parsing required fields for scatter-gather patterns
AmirhosseinPoolad Jul 25, 2025
d018b75
Add scatter-gather pattern strong test
AmirhosseinPoolad Jul 25, 2025
9eb6d02
Fix set_switch_func_type bug
AmirhosseinPoolad Jul 25, 2025
25d1db6
Fix commenting issues
AmirhosseinPoolad Jul 30, 2025
5d523a8
Improve scatter-gather documentation
AmirhosseinPoolad Jul 31, 2025
3c2e09b
Add some comments to scatter-gather parsing
AmirhosseinPoolad Jul 31, 2025
1f5457f
Add comments to the scatter-gather test architecture
AmirhosseinPoolad Jul 31, 2025
e1a777a
Improve comments for scatter-gather parsing
AmirhosseinPoolad Jul 31, 2025
351b3a3
Add figure to scatter-gather docs
AmirhosseinPoolad Aug 5, 2025
44d1078
Address scatter-gather PR comments
AmirhosseinPoolad Aug 5, 2025
0be355d
[Scatter-Gather] Add inline to enum-string maps
AmirhosseinPoolad Aug 12, 2025
bf244ed
[SG] Fix typo in architecture reference
AmirhosseinPoolad Aug 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 109 additions & 1 deletion doc/src/arch/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2485,7 +2485,7 @@ The full format is documented below.
Defined under the ``<switchfuncs>`` XML node, one or more ``<func...>`` entries is used to specify permutation functions that connect different sides of a switch block.


.. arch:tag:: <wireconn num_conns="expr" from_type="string, string, string, ..." to_type="string, string, string, ..." from_switchpoint="int, int, int, ..." to_switchpoint="int, int, int, ..." from_order="{fixed | shuffled}" to_order="{fixed | shuffled}" switch_override="string"/>
.. arch:tag:: <wireconn num_conns="expr" from_type="string, string, string, ..." to_type="string, string, string, ..." from_switchpoint="int, int, int, ..." to_switchpoint="int, int, int, ..." from_order="{fixed | shuffled}" to_order="{fixed | shuffled}" switch_override="string" side="string"/>

:req_param num_conns:
Specifies how many connections should be created between the from_type/from_switchpoint set and the to_type/to_switchpoint set.
Expand Down Expand Up @@ -2592,6 +2592,10 @@ The full format is documented below.
By using a zero-delay and zero-resistance switch one can also create T and L shaped wire segments.

**Default:** If no override is specified, the usual wire_switch that drives the ``to`` wire will be used.

:opt_param side:
Specifies the sides that connections are gathered from or scattered to. Valid sides are right, left, top and bottom and are represented by characters 'r', 'l', 't' and 'b'.
For example, to select connections from right, left and bottom set this attribute to "rlb". Note that this attribute is used only for :ref:`scatter_gather_patterns` and does not do anything for other usages of this tag.

.. arch:tag:: <from type="string" switchpoint="int, int, int, ..."/>

Expand Down Expand Up @@ -2634,6 +2638,110 @@ The full format is documented below.
The 'to' set is all L4 switchpoint 0's.
Note that since different switchpoints are selected from different segment types it is not possible to specify this without using ``<from>`` sub-tags.

.. _scatter_gather_patterns:

Scatter-Gather Patterns
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recall during the weekly VTR meetings, we discussed putting this in the layout tag. I believe that this tag should be a part of the layout in some way, since the location of these SG patterns do depend on the layout of the device (see my comment on sg_location). This should be updated in the text somewhere in here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the way custom switchblocks work. It's also a bit easier to implement since it leaves the layout parsing code untouched.

---------------------

The content under the ``<scatter_gather_list>`` tag consists of one or more ``<sg_pattern>`` tags that are used to specify a scatter-gather pattern.
Scatter-gather patterns can be used to specify multi-level switch patterns, rather than the direct wire-to-wire switch patterns of conventional switch blocks.
These additional switches, wires and/or muxes will be added to the architecture, augmenting wires created using segment specifications and swiches created using switch box specifications.
The number of any additional wires or muxes created by scatter-gather specifications will not vary with routing channel width.

.. figure:: scatter_gather_images/scattergather_diagram.svg

Overview of how scatter-gather patterns work. First, connections from a switchblock location are selected according to the specification.
These selected connection are then muxed and passed through the scatter-gather node, which is typically a wire segment. The scatter-gather node then fans out or scatters in another switchblock location.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give high-level context on why we need this (specify multi-level switch patterns, rather than the direct wire-to-wire switch patterns of conventional switch blocks). These additional switches, wires and/or muxes will be added to the architecture, augmenting wires created using segment specifications and swiches created using switch box specifications.
The number of any additional wires or muxes created by scatter-gather specifications will not vary with routing channel width.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High-level spec needs a picture showing the gather pattern, the scatter-gather node, and the scatter pattern. Link that to the high-level description.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a figure giving a visual overview of scatter-gather patterns.

.. note:: Scatter-Gather patterns are work in progress and experimental. Currently, VPR does not support this specification and using this tag would not result in any modifications to the RR-Graph.

When instantiated, a scatter-gather pattern gathers connections from a switchblock and passes the connection through a multiplexer and the scatter-gather node which is typically a wire segment, then scatters or fans out somewhere else in the device. These patterns can be used to define 3D switchblocks. An example is shown below:

.. code-block:: xml

<scatter_gather_list>
<sg_pattern name="name" type="unidir">
<gather>
<!-- Gather 30 connections from the 0, 4, 8 or 12 position of L16 wires of all four sides of a switchblock location -->
<wireconn num_conns="30" from_type="L16" from_switchpoint="0,12,8,4" side="rltb"/>
<gather/>

<scatter>
<!-- Scatter 30 connections to the starting position of L16 wires of all four sides of a switchblock location -->
<wireconn num_conns="30" to_type="L16" to_switchpoint="0" side="rtlb"/>
<scatter/>

<sg_link_list>
<!-- Link going up one layer, using the '3D_SB_MUX' multiplexer to gather connections from the bottom layer and using the 'TSV' node/wire to move up one layer -->
<sg_link name="L_UP" z_offset="1" x_offset="0" y_offset="0" mux="3D_SB_MUX" seg_type="TSV"/>
<!-- Same as above but moving one layer down -->
<sg_link name="L_DOWN" z_offset="-1" mux="3D_SB_MUX" seg_type="TSV"/>
<sg_link_list/>

<!-- Instantiate 10 'L_UP' sg_links per switchblock location everywhere on the device -->
<sg_location type="EVERYWHERE" num="10" sg_link="L_UP"/>
<!-- Instantiate 10 'L_DOWN' sg_links per switchblock location everywhere on the device -->
<sg_location type="EVERYWHERE" num="10" sg_link="L_DOWN"/>
<sg_pattern/>

<sg_pattern name="interposer_conn_sg" type="bidir">
... <!-- Another scatter-gather pattern specification -->
<sg_pattern/>
<scatter_gather_list/>

.. arch:tag:: <sg_pattern name="string" type={unidir|bidir}>

:req_param name: A unique alphanumeric string
:req_param type: ``unidir`` or ``bidir``.

'unidir': Added connections are unidirectional; all the gather connections are combined in a mux that then drives the scatter-gather node which in turn drives the wires specified in the scatter specification.

'bidir': The gather and scatter connections are mirrored; the same scatter pattern is implemented at each end of the scatter-gather pattern. This implies the two muxes driving the scatter-gather node can have their outputs tri-stated.

.. arch:tag:: <gather>

Contains a <wireconn> tag specifying how the fan-in or gather connections are selected. See ``wireconn`` for the relevant specification.
This <wireconn> tag supports an additioinal 'side' attribute which selects the sides that connections are gathered from (e.g. any of the top, bottom, left and right).

.. arch:tag:: <scatter>

Contains a <wireconn> tag specifying how the fan-out or scatter connections are selected. See ``wireconn`` for the relevant specification.
This <wireconn> tag supports an additioinal 'side' attribute which selects the sides that connections are scattered to (e.g. any of the top, bottom, left and right).

.. arch:tag:: <sg_link_list>

Contains one or more <sg_link> tags specifying how the pattern of how connections move from the gather location to the scatter location i.e. defines the used mux and scatter-gather node.

.. note:: <sg_link> tags are not instantiations of the pattern and would not result in any changes to the device. instead, the <sg_location> tag instantiates the pattern and selects one of the <sg_link> tags to be used for the instantiation.

.. arch:tag:: <sg_link name="string" x_offset="int" y_offset="int" z_offset="int" mux="string" seg_type="string">

:req_param name: A unique alphanumeric string
:req_param mux: Name of the multiplexer used to gather connections
:req_param seg_type: Name of the segment/wire used to move through the device to the scatter location (i.e. the type of the scatter-gather node)

:opt_param x_offset: Offset of the scatter relative to the gather in the x-axis
:opt_param y_offset: Offset of the scatter relative to the gather in the y-axis
:opt_param z_offset: Offset of the scatter relative to the gather in the z-axis

.. note:: One and only one of the offset fields for the sg_link tag should be set. The magnitude of the offset will generally be chosen by the architecture file creator to match the length of the sg_link segment type.

.. arch:tag:: <sg_location type="string" num="int" sg_link_name="string">

Instantiates the scatter-gather pattern with the specified sg_link.

:req_param num: number of scatter-gather instances per matching location (e.g. per switch block)
:req_param sg_link_name: name of the sg_link used in the instantiation
:req_param type: Can be one of the following strings:

* ``EVERYWHERE`` – at each switch block of the FPGA
* ``PERIMETER`` – at each perimeter switch block (x-directed and/or y-directed channel segments may terminate here)
* ``CORNER`` – only at the corner switch blocks (both x and y-directed channels terminate here)
* ``FRINGE`` – same as PERIMETER but excludes corners
* ``CORE`` – everywhere but the perimeter
Sets the location on the FPGA where the connections described by this scatter-gather pattern be instantiated.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little confused how this corresponds with "location". For me, location would imply an (x, y) tile position of some sort, not general groups.

Is there any way that we can allow this specification to allow the user to only allow a particular SG pattern to exist at a specified tile coordinate? Perhaps it can be similar to the fixed-layout specification of tiles. It seems a little rigid to assign locations this way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses the same syntax as the custom switch blocks. There is an XY_SPECIFIED option which offers more control but since it's not documented (yet) I didn't want to add it here.



.. _arch_metadata:

Architecture metadata
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions doc/src/arch/scatter_gather_images/scattergather_diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 72 additions & 88 deletions libs/libarchfpga/src/parse_switchblocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,6 @@ using vtr::t_formula_data;
/**** Function Declarations ****/
/*---- Functions for Parsing Switchblocks from Architecture ----*/

/**
* @brief Parses a wireconn node and returns a `t_wireconn_inf` structure.
*
* Determines whether the wireconn is in inline or multi-node format and dispatches
* to the appropriate parsing subroutine.
*
* @param node XML node representing the wireconn.
* @param loc_data Location data for error reporting.
* @param switches List of architecture switch definitions (used for switch overrides).
* @return A `t_wireconn_inf` structure populated with parsed data.
*/
static t_wireconn_inf parse_wireconn(pugi::xml_node node, const pugiutil::loc_data& loc_data, const std::vector<t_arch_switch_inf>& switches);

//Process the desired order of a wireconn
static void parse_switchpoint_order(const char* order, SwitchPointOrder& switchpoint_order);

Expand All @@ -60,11 +47,15 @@ static void parse_switchpoint_order(const char* order, SwitchPointOrder& switchp
* @param node XML node containing inline wireconn attributes.
* @param loc_data Location data for error reporting.
* @param switches List of architecture switch definitions (used for switch overrides).
* @param can_skip_from_or_to Determines if the from or to attributes are optional or mandatory.
* <wireconn> tags for switch blocks require both from and to attributes while those for
* scatter-gather use one or the other.
* @return A `t_wireconn_inf` structure populated with parsed data.
*/
static t_wireconn_inf parse_wireconn_inline(pugi::xml_node node,
const pugiutil::loc_data& loc_data,
const std::vector<t_arch_switch_inf>& switches);
const std::vector<t_arch_switch_inf>& switches,
bool can_skip_from_or_to);

/**
* @brief Parses a multi-node `<wireconn>` definition with `<from>` and `<to>` children.
Expand Down Expand Up @@ -137,49 +128,77 @@ void read_sb_wireconns(const std::vector<t_arch_switch_inf>& switches,
}
}

static t_wireconn_inf parse_wireconn(pugi::xml_node node,
const pugiutil::loc_data& loc_data,
const std::vector<t_arch_switch_inf>& switches) {
t_wireconn_inf parse_wireconn(pugi::xml_node node,
const pugiutil::loc_data& loc_data,
const std::vector<t_arch_switch_inf>& switches,
bool can_skip_from_or_to) {

size_t num_children = count_children(node, "from", loc_data, ReqOpt::OPTIONAL);
num_children += count_children(node, "to", loc_data, ReqOpt::OPTIONAL);

t_wireconn_inf wireconn;
if (num_children == 0) {
return parse_wireconn_inline(node, loc_data, switches);
wireconn = parse_wireconn_inline(node, loc_data, switches, can_skip_from_or_to);
} else {
VTR_ASSERT(num_children > 0);
return parse_wireconn_multinode(node, loc_data, switches);
wireconn = parse_wireconn_multinode(node, loc_data, switches);
}

// Parse the optional "side" field of the <wireconn> tag
std::string sides_string = get_attribute(node, "side", loc_data, pugiutil::OPTIONAL).as_string();

if (sides_string.find_first_not_of("rtlbRTLB") != std::string::npos) {
archfpga_throw(loc_data.filename_c_str(), loc_data.line(node), "Unknown side specified: %s\n", sides_string.c_str());
}
for (char side_char : sides_string) {
wireconn.sides.insert(CHAR_SIDE_MAP.at(side_char));
}

return wireconn;
}

static t_wireconn_inf parse_wireconn_inline(pugi::xml_node node,
const pugiutil::loc_data& loc_data,
const std::vector<t_arch_switch_inf>& switches) {
const std::vector<t_arch_switch_inf>& switches,
bool can_skip_from_or_to) {

// Parse an inline wireconn definition, using attributes
expect_only_attributes(node, {"num_conns", "from_type", "to_type", "from_switchpoint", "to_switchpoint", "from_order", "to_order", "switch_override"}, loc_data);
expect_only_attributes(node,
{"num_conns", "from_type", "to_type", "from_switchpoint",
"to_switchpoint", "from_order", "to_order", "switch_override", "side"},
loc_data);

t_wireconn_inf wc;

ReqOpt from_to_required = can_skip_from_or_to ? ReqOpt::OPTIONAL : ReqOpt::REQUIRED;

// get the connection style
const char* char_prop = get_attribute(node, "num_conns", loc_data).value();
parse_num_conns(char_prop, wc);

// get from type
char_prop = get_attribute(node, "from_type", loc_data).value();
parse_comma_separated_wire_types(char_prop, wc.from_switchpoint_set);
char_prop = get_attribute(node, "from_type", loc_data, from_to_required).value();
if (!can_skip_from_or_to) {
parse_comma_separated_wire_types(char_prop, wc.from_switchpoint_set);
}

// get to type
char_prop = get_attribute(node, "to_type", loc_data).value();
parse_comma_separated_wire_types(char_prop, wc.to_switchpoint_set);
char_prop = get_attribute(node, "to_type", loc_data, from_to_required).value();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if someone did not specify either a from_type or a to_type? Is that acceptable? If not, we should throw an error here during parsing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the way I did this is the can_skip_from_or_to argument. By default it is set to false and from/to_type are required attributes, this is used by custom switch blocks and therefore the behavior for those aren't changed at all. For scatter-gather specifications, the error checking is done outside of this function and in process_sg_tag.

Added checking for completely empty fields in process_sg_tag.

if (!can_skip_from_or_to) {
parse_comma_separated_wire_types(char_prop, wc.to_switchpoint_set);
}

// get the source wire point
char_prop = get_attribute(node, "from_switchpoint", loc_data).value();
parse_comma_separated_wire_points(char_prop, wc.from_switchpoint_set);
char_prop = get_attribute(node, "from_switchpoint", loc_data, from_to_required).value();
if (!can_skip_from_or_to) {
parse_comma_separated_wire_points(char_prop, wc.from_switchpoint_set);
}

// get the destination wire point
char_prop = get_attribute(node, "to_switchpoint", loc_data).value();
parse_comma_separated_wire_points(char_prop, wc.to_switchpoint_set);
char_prop = get_attribute(node, "to_switchpoint", loc_data, from_to_required).value();
if (!can_skip_from_or_to) {
parse_comma_separated_wire_points(char_prop, wc.to_switchpoint_set);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More error checking is needed. For example, for scatter-gather patterns, a combination of from and to in <wireconn> is illegal. I think the caller of this function should specify what fields are allowed in <wireconn>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checking is done in the "process_sg_tag" function. I don't think it makes sense for the wireconn tag to legalize itself based on what's around it, the parent tag should be the one doing the checking.


char_prop = get_attribute(node, "from_order", loc_data, ReqOpt::OPTIONAL).value();
parse_switchpoint_order(char_prop, wc.from_switchpoint_order);
Expand Down Expand Up @@ -315,66 +334,31 @@ static void parse_num_conns(std::string num_conns, t_wireconn_inf& wireconn) {

//set sides for a specific conn for custom switch block pattern
static void set_switch_func_type(SBSideConnection& conn, const char* func_type) {
if (0 == strcmp(func_type, "lt")) {
conn.set_sides(LEFT, TOP);
} else if (0 == strcmp(func_type, "lr")) {
conn.set_sides(LEFT, RIGHT);
} else if (0 == strcmp(func_type, "lb")) {
conn.set_sides(LEFT, BOTTOM);
} else if (0 == strcmp(func_type, "la")) {
conn.set_sides(LEFT, ABOVE);
} else if (0 == strcmp(func_type, "lu")) {
conn.set_sides(LEFT, UNDER);
} else if (0 == strcmp(func_type, "tl")) {
conn.set_sides(TOP, LEFT);
} else if (0 == strcmp(func_type, "tb")) {
conn.set_sides(TOP, BOTTOM);
} else if (0 == strcmp(func_type, "tr")) {
conn.set_sides(TOP, RIGHT);
} else if (0 == strcmp(func_type, "ta")) {
conn.set_sides(TOP, ABOVE);
} else if (0 == strcmp(func_type, "tu")) {
conn.set_sides(TOP, UNDER);
} else if (0 == strcmp(func_type, "rt")) {
conn.set_sides(RIGHT, TOP);
} else if (0 == strcmp(func_type, "rl")) {
conn.set_sides(RIGHT, LEFT);
} else if (0 == strcmp(func_type, "rb")) {
conn.set_sides(RIGHT, BOTTOM);
} else if (0 == strcmp(func_type, "ra")) {
conn.set_sides(RIGHT, ABOVE);
} else if (0 == strcmp(func_type, "ru")) {
conn.set_sides(RIGHT, UNDER);
} else if (0 == strcmp(func_type, "bl")) {
conn.set_sides(BOTTOM, LEFT);
} else if (0 == strcmp(func_type, "bt")) {
conn.set_sides(BOTTOM, TOP);
} else if (0 == strcmp(func_type, "br")) {
conn.set_sides(BOTTOM, RIGHT);
} else if (0 == strcmp(func_type, "ba")) {
conn.set_sides(BOTTOM, ABOVE);
} else if (0 == strcmp(func_type, "bu")) {
conn.set_sides(BOTTOM, UNDER);
} else if (0 == strcmp(func_type, "al")) {
conn.set_sides(ABOVE, LEFT);
} else if (0 == strcmp(func_type, "at")) {
conn.set_sides(ABOVE, TOP);
} else if (0 == strcmp(func_type, "ar")) {
conn.set_sides(ABOVE, RIGHT);
} else if (0 == strcmp(func_type, "ab")) {
conn.set_sides(ABOVE, BOTTOM);
} else if (0 == strcmp(func_type, "ul")) {
conn.set_sides(UNDER, LEFT);
} else if (0 == strcmp(func_type, "ut")) {
conn.set_sides(UNDER, TOP);
} else if (0 == strcmp(func_type, "ur")) {
conn.set_sides(UNDER, RIGHT);
} else if (0 == strcmp(func_type, "ub")) {
conn.set_sides(UNDER, BOTTOM);
} else {
/* unknown permutation function */
archfpga_throw(__FILE__, __LINE__, "Unknown permutation function specified: %s\n", func_type);

if (std::string(func_type).length() != 2) {
archfpga_throw(__FILE__, __LINE__, "Custom switchblock func type must be 2 characters long: %s\n", func_type);
}

// Only valid sides are right, top, left, bottom, above and under
if (std::string(func_type).find_first_not_of("rtlbauRTLBAU") != std::string::npos) {
archfpga_throw(__FILE__, __LINE__, "Unknown direction specified: %s\n", func_type);
}

e_side from_side = CHAR_SIDE_MAP.at(func_type[0]);
e_side to_side = CHAR_SIDE_MAP.at(func_type[1]);

// Can't go from side to same side
if (to_side == from_side) {
archfpga_throw(__FILE__, __LINE__, "Unknown permutation function specified, cannot go from side to same side: %s\n", func_type);
}

// We don't allow specification of patterns that imply edges going over 2 or more layers
// (this doesn't seem electrically logical), so we disallow going from above/under to above/under.
if ((to_side == ABOVE || to_side == UNDER) && (from_side == ABOVE || from_side == UNDER)) {
archfpga_throw(__FILE__, __LINE__, "Unknown permutation function specified, cannot go from above/under to above/under: %s\n", func_type);
}

conn.set_sides(from_side, to_side);
}

void read_sb_switchfuncs(pugi::xml_node node, t_switchblock_inf& sb, const pugiutil::loc_data& loc_data) {
Expand Down
Loading