Initial import of libdpf.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 14:08:32 -06:00
commit e4e666f459
4563 changed files with 1690372 additions and 0 deletions

4
.deepsource.toml Normal file
View file

@ -0,0 +1,4 @@
version = 1
[[analyzers]]
name = "cxx"

59
.gitignore vendored Normal file
View file

@ -0,0 +1,59 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# CMake build tree
test/build/
# Doxygen
build/docs/**
doc/doxygen.log
doc/libdpf.tag.xml
doc/libdpf_full.md
# Visual Studio Code
.vscode
.vscode/**
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# CPP lint
__pycache__/**

22
.gitmodules vendored Normal file
View file

@ -0,0 +1,22 @@
[submodule "thirdparty/hedley"]
path = thirdparty/hedley
url = https://github.com/DigitalLibertiesLab/hedley.git
[submodule "thirdparty/portable-snippets"]
path = thirdparty/portable-snippets
url = https://github.com/DigitalLibertiesLab/portable-snippets.git
[submodule "thirdparty/doxygen-awesome-css"]
path = thirdparty/doxygen-awesome-css
url = https://github.com/DigitalLibertiesLab/doxygen-awesome-css.git
branch = libdpf
[submodule "thirdparty/json"]
path = thirdparty/json
url = https://github.com/DigitalLibertiesLab/json.git
[submodule "thirdparty/simde"]
path = thirdparty/simde
url = https://github.com/DigitalLibertiesLab/simde.git
[submodule "thirdparty/asio"]
path = thirdparty/asio
url = https://github.com/DigitalLibertiesLab/asio.git
[submodule "test/googletest"]
path = test/googletest
url = https://github.com/google/googletest.git

17
AUTHORS.md Normal file
View file

@ -0,0 +1,17 @@
<!-- # Authors {#authors} -->
- Ryan Henry
&emsp;<i class="fa-solid fa-envelope"></i>&nbsp;<ryan.henry@ucalgary.ca>
- Christopher Jiang
&emsp;<i class="fa-solid fa-envelope"></i>&nbsp;<christopher.jiang@ucalgary.ca>
- Kyle Storrier
&emsp;<i class="fa-solid fa-envelope"></i>&nbsp;<kyle.storrier@ucalgary.ca>
- Adithya Vadapalli
&emsp;<i class="fa-solid fa-envelope"></i>&nbsp;<adithya.vadapalli@uwaterloo.ca>

2
BUGS.md Normal file
View file

@ -0,0 +1,2 @@
<!-- # Bugs {#bugs} -->

1
CHANGES.md Normal file
View file

@ -0,0 +1 @@
<!-- # CHANGELOG {#changes} -->

14
CPPLINT.cfg Normal file
View file

@ -0,0 +1,14 @@
# Don't search for additional CPPLINT.cfg in parent directories.
set noparent
# include project root in path construction (for header guards)
root=..
# { should almost always be on a new line
filter=-whitespace/braces
# Scope specifiers are indent +2 spaces
filter=-whitespace/indent
# An else should appear on the same line as the preceding }
filter=-whitespace/newline

362
LICENSE.md Normal file
View file

@ -0,0 +1,362 @@
### GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
### Preamble
The licenses for most software are designed to take away your freedom
to share and change it. By contrast, the GNU General Public License is
intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if
you distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on,
we want its recipients to know that what they have is not the
original, so that any problems introduced by others will not reflect
on the original authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at
all.
The precise terms and conditions for copying, distribution and
modification follow.
### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
**0.** This License applies to any program or other work which
contains a notice placed by the copyright holder saying it may be
distributed under the terms of this General Public License. The
"Program", below, refers to any such program or work, and a "work
based on the Program" means either the Program or any derivative work
under copyright law: that is to say, a work containing the Program or
a portion of it, either verbatim or with modifications and/or
translated into another language. (Hereinafter, translation is
included without limitation in the term "modification".) Each licensee
is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the Program
(independent of having been made by running the Program). Whether that
is true depends on what the Program does.
**1.** You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a
fee.
**2.** You may modify your copy or copies of the Program or any
portion of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
**a)** You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
**b)** You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any part
thereof, to be licensed as a whole at no charge to all third parties
under the terms of this License.
**c)** If the modified program normally reads commands interactively
when run, you must cause it, when started running for such interactive
use in the most ordinary way, to print or display an announcement
including an appropriate copyright notice and a notice that there is
no warranty (or else, saying that you provide a warranty) and that
users may redistribute the program under these conditions, and telling
the user how to view a copy of this License. (Exception: if the
Program itself is interactive but does not normally print such an
announcement, your work based on the Program is not required to print
an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
**3.** You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
**a)** Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections 1
and 2 above on a medium customarily used for software interchange; or,
**b)** Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your cost of
physically performing source distribution, a complete machine-readable
copy of the corresponding source code, to be distributed under the
terms of Sections 1 and 2 above on a medium customarily used for
software interchange; or,
**c)** Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is allowed
only for noncommercial distribution and only if you received the
program in object code or executable form with such an offer, in
accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
**4.** You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt otherwise
to copy, modify, sublicense or distribute the Program is void, and
will automatically terminate your rights under this License. However,
parties who have received copies, or rights, from you under this
License will not have their licenses terminated so long as such
parties remain in full compliance.
**5.** You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
**6.** Each time you redistribute the Program (or any work based on
the Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
**7.** If, as a consequence of a court judgment or allegation of
patent infringement or for any other reason (not limited to patent
issues), conditions are imposed on you (whether by court order,
agreement or otherwise) that contradict the conditions of this
License, they do not excuse you from the conditions of this License.
If you cannot distribute so as to satisfy simultaneously your
obligations under this License and any other pertinent obligations,
then as a consequence you may not distribute the Program at all. For
example, if a patent license would not permit royalty-free
redistribution of the Program by all those who receive copies directly
or indirectly through you, then the only way you could satisfy both it
and this License would be to refrain entirely from distribution of the
Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
**8.** If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
**9.** The Free Software Foundation may publish revised and/or new
versions of the General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Program does not specify a
version number of this License, you may choose any version ever
published by the Free Software Foundation.
**10.** If you wish to incorporate parts of the Program into other
free programs whose distribution conditions are different, write to
the author to ask for permission. For software which is copyrighted by
the Free Software Foundation, write to the Free Software Foundation;
we sometimes make exceptions for this. Our decision will be guided by
the two goals of preserving the free status of all derivatives of our
free software and of promoting the sharing and reuse of software
generally.
**NO WARRANTY**
**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
### END OF TERMS AND CONDITIONS
### How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
one line to give the program's name and an idea of what it does.
Copyright (C) yyyy name of author
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Also add information on how to contact you by electronic and paper
mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
type `show w'. This is free software, and you are welcome
to redistribute it under certain conditions; type `show c'
for details.
The hypothetical commands \`show w' and \`show c' should show the
appropriate parts of the General Public License. Of course, the
commands you use may be called something other than \`show w' and
\`show c'; they could even be mouse-clicks or menu items--whatever
suits your program.
You should also get your employer (if you work as a programmer) or
your school, if any, to sign a "copyright disclaimer" for the program,
if necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright
interest in the program `Gnomovision'
(which makes passes at compilers) written
by James Hacker.
signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library,
you may consider it more useful to permit linking proprietary
applications with the library. If this is what you want to do, use the
[GNU Lesser General Public
License](https://www.gnu.org/licenses/lgpl.html) instead of this
License.
Running

18
Makefile Normal file
View file

@ -0,0 +1,18 @@
.PHONY: docs
docs:
@mkdir -p build/docs
@sed -e '/%LIBDPF_INCLUDE_GETTING_STARTED_INTRODUCTION%/{r doc/pages/introduction.md' -e 'd}' doc/libdpf.md > doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_GETTING_DPF_BASICS%/{r doc/pages/basics.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_GETTING_STARTED_INPUT_TYPES%/{r doc/pages/input_types.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_GETTING_STARTED_OUTPUT_TYPES%/{r doc/pages/output_types.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_GETTING_STARTED_EVALUATION%/{r doc/pages/evaluation.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_GETTING_STARTED_ITERABLES%/{r doc/pages/iterables.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_PIR_PIR1%/{r doc/pages/pir1.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_MISC_BUGS%/{r BUGS.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_MISC_CHANGES%/{r CHANGES.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_MISC_TODO%/{r TODO.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_MISC_SUBMODULES%/{r doc/pages/submodules.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_MISC_AUTHORS%/{r AUTHORS.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_MISC_LICENSE%/{r LICENSE.md' -e 'd}' doc/libdpf_full.md
@sed -i -e '/%LIBDPF_INCLUDE_CODE_LISTINGS%/{r doc/pages/listings.md' -e 'd}' doc/libdpf_full.md
@doxygen doc/Doxyfile

5
TODO.md Normal file
View file

@ -0,0 +1,5 @@
<!-- # TODO list {#todo} -->
- <i class="fa-regular fa-square"></i> foo
- <i class="fa-solid fa-square-check"></i> bar

2808
doc/Doxyfile Normal file

File diff suppressed because it is too large Load diff

220
doc/DoxygenLayout.xml Normal file
View file

@ -0,0 +1,220 @@
<doxygenlayout version="1.0">
<!-- Generated by doxygen 1.9.6 -->
<!-- Navigation index tabs for HTML output -->
<navindex>
<tab type="mainpage" visible="yes" title=""/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="examples" visible="no" title="Code Listings" intro=""/>
<tab type="usergroup" visible="yes" title="API reference">
<tab type="structs" visible="no" title="">
<tab type="structlist" visible="yes" title="" intro=""/>
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
</tab>
<tab type="classlist" visible="yes" title="List of classes" intro=""/>
<tab type="filelist" visible="yes" title="List of files" intro=""/>
</tab>
<!-- <tab type="user" visible="yes" url="@ref BibTeX" title="Citing libdpf++"/> -->
<!-- <tab type="user" visible="yes" url="../grotto" title="Switch to Gʀᴏᴛᴛᴏ"/> -->
<!-- <tab type="user" visible="yes" url="../subleq" title="Switch to Sᴜʙʟᴇǫ"/> -->
</navindex>
<!-- Layout definition for a class page -->
<class>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<inheritancegraph visible="$CLASS_GRAPH"/>
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
<detaileddescription title=""/>
<memberdecl>
<nestedclasses visible="yes" title=""/>
<publictypes title=""/>
<services title=""/>
<interfaces title=""/>
<publicslots title=""/>
<signals title=""/>
<publicmethods title=""/>
<publicstaticmethods title=""/>
<publicattributes title=""/>
<publicstaticattributes title=""/>
<protectedtypes title=""/>
<protectedslots title=""/>
<protectedmethods title=""/>
<protectedstaticmethods title=""/>
<protectedattributes title=""/>
<protectedstaticattributes title=""/>
<packagetypes title=""/>
<packagemethods title=""/>
<packagestaticmethods title=""/>
<packageattributes title=""/>
<packagestaticattributes title=""/>
<properties title=""/>
<events title=""/>
<privatetypes title=""/>
<privateslots title=""/>
<privatemethods title=""/>
<privatestaticmethods title=""/>
<privateattributes title=""/>
<privatestaticattributes title=""/>
<friends title=""/>
<related title="" subtitle=""/>
<membergroups visible="yes"/>
</memberdecl>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
<enums title=""/>
<services title=""/>
<interfaces title=""/>
<constructors title=""/>
<functions title=""/>
<related title=""/>
<variables title=""/>
<properties title=""/>
<events title=""/>
</memberdef>
<allmemberslink visible="yes"/>
<usedfiles visible="$SHOW_USED_FILES"/>
<authorsection visible="yes"/>
</class>
<!-- Layout definition for a namespace page -->
<namespace>
<briefdescription visible="yes"/>
<detaileddescription title=""/>
<memberdecl>
<nestednamespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<concepts visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<membergroups visible="yes"/>
</memberdecl>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
</memberdef>
<authorsection visible="yes"/>
</namespace>
<!-- Layout definition for a concept page -->
<concept>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<definition visible="yes" title=""/>
<detaileddescription title=""/>
<authorsection visible="yes"/>
</concept>
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includegraph visible="$INCLUDE_GRAPH"/>
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
<sourcelink visible="yes"/>
<detaileddescription title=""/>
<memberdecl>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<membergroups visible="yes"/>
</memberdecl>
<memberdef>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
</memberdef>
<authorsection/>
</file>
<!-- Layout definition for a group page -->
<group>
<briefdescription visible="yes"/>
<groupgraph visible="$GROUP_GRAPHS"/>
<detaileddescription title=""/>
<memberdecl>
<nestedgroups visible="yes" title=""/>
<dirs visible="yes" title=""/>
<files visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<classes visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
<membergroups visible="yes"/>
</memberdecl>
<memberdef>
<pagedocs/>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
</memberdef>
<authorsection visible="yes"/>
</group>
<!-- Layout definition for a directory page -->
<directory>
<briefdescription visible="yes"/>
<directorygraph visible="yes"/>
<detaileddescription title=""/>
<memberdecl>
<dirs visible="yes"/>
<files visible="yes"/>
</memberdecl>
</directory>
</doxygenlayout>

BIN
doc/assets/libdpf-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
doc/assets/libdpf.psd Normal file

Binary file not shown.

32
doc/directories.dox Normal file
View file

@ -0,0 +1,32 @@
/// @dir ./doc
/// @brief input files used by `Doxygen` to produce this documentation
/// @dir ./doc/assets
/// @brief image files
/// @dir ./examples
/// @brief sample code listings to demonstrate use of various `libdpf++` features
/// @dir ./examples/evaluation
/// @brief evaluation
/// @dir ./examples/input_types
/// @brief inputs
/// @dir ./examples/output_types
/// @brief outputs
/// @dir ./examples/iterables
/// @brief iterable
/// @dir ./include
/// @brief location of `dph.hpp` and many other `libdpf++` headers in `include/dpf`
/// @dir ./include/dpf
/// @brief location of most `libdpf++` headers
/// @dir ./test
/// @brief collection of `GTest` test cases
/// @dir ./thirdparty
/// @brief submodules and other third-party content

102
doc/examples.dox Normal file
View file

@ -0,0 +1,102 @@
// input_types
/// @{
/// @example input_types/integral_types.cpp integral_types.cpp
/// @brief an example of `int` in use
/// @example input_types/extended_types.cpp extended_types.cpp
/// @brief an example of `int128_t` in use
/// @example input_types/modint.cpp modint.cpp
/// @brief an example of `dpf::modint` in use
/// @example input_types/bitstring.cpp bitstring.cpp
/// @brief an example of `dpf::bitstring` in use
/// @example input_types/keyword.cpp keyword.cpp
/// @brief an example of `dpf::keyword` in use
/// @example input_types/xor_wrapper.cpp xor_wrapper.cpp
/// @brief an example of `dpf::memoizers` in use
/// @example input_types/custom.cpp custom.cpp
/// @brief an example of `dpf::output_buffers` in use
/// @}
// output_types
/// @{
/// @example output_types/integral_types.cpp integral_types.cpp
/// @brief an example of `dpf::eval_point` in use
/// @example output_types/extended_types.cpp extended_types.cpp
/// @brief an example of `dpf::eval_interval` in use
/// @example output_types/bit.cpp bit.cpp
/// @brief an example of `dpf::eval_full` in use
/// @example output_types/bitstring.cpp bitstring.cpp
/// @brief an example of `dpf::eval_sequence` in use
/// @example output_types/wildcard.cpp wildcard.cpp
/// @brief an example of `dpf::eval_sequence` in use
/// @example output_types/xor_wrapper.cpp xor_wrapper.cpp
/// @brief an example of `dpf::memoizers` in use
/// @example output_types/custom.cpp custom.cpp
/// @brief an example of `dpf::output_buffers` in use
/// @}
// evaluations
/// @{
/// @example evaluation/eval_point.cpp eval_point.cpp
/// @brief an example of `dpf::eval_point` in use
/// @example evaluation/eval_interval.cpp eval_interval.cpp
/// @brief an example of `dpf::eval_interval` in use
/// @example evaluation/eval_full.cpp eval_full.cpp
/// @brief an example of `dpf::eval_full` in use
/// @example evaluation/eval_sequence.cpp eval_sequence.cpp
/// @brief an example of `dpf::eval_sequence` in use
/// @example evaluation/memoizers.cpp memoizers.cpp
/// @brief an example of `dpf::memoizers` in use
/// @example evaluation/output_buffers.cpp output_buffers.cpp
/// @brief an example of `dpf::output_buffers` in use
/// @}
/// @{
/// @example iterables/setbit_index_iterable.cpp setbit_index_iterable.cpp
/// @brief an example of `dpf::setbit_index_iterable` in use
/// @example iterables/advice_bit_iterable.cpp advice_bit_iterable.cpp
/// @brief an example of `dpf::advice_bit_iterable` in use
/// @example iterables/parallel_bit_iterable.cpp parallel_bit_iterable.cpp
/// @brief an example of `dpf::parallel_bit_iterable` in use
/// @example iterables/subinterval_iterable.cpp subinterval_iterable.cpp
/// @brief an example of `dpf::subinterval_iterable` in use
/// @example iterables/subsequence_iterable.cpp subsequence_iterable.cpp
/// @brief an example of `dpf::subsequence_iterable` in use
/// @example iterables/zip_iterable.cpp zip_iterable.cpp
/// @brief an example of `dpf::zip_iterable` in use
/// @}

18
doc/footer.html Normal file
View file

@ -0,0 +1,18 @@
<!-- HTML footer for doxygen 1.9.6-->
<!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion </li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/><address class="footer"><small>
$generatedby&#160;<a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion
</small></address>
<!--END !GENERATE_TREEVIEW-->
</body>
</html>

64
doc/header.html Normal file
View file

@ -0,0 +1,64 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<link rel="icon" type="image/svg+xml" href="libdpf-logo.svg"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-fragment-copy-button.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-paragraph-link.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-interactive-toc.js"></script>
<script type="text/javascript" src="$relpath^doxygen-awesome-tabs.js"></script>
<script type="text/javascript">
DoxygenAwesomeFragmentCopyButton.init()
DoxygenAwesomeDarkModeToggle.init()
DoxygenAwesomeParagraphLink.init()
DoxygenAwesomeInteractiveToc.init()
DoxygenAwesomeTabs.init()
</script>
$treeview
$search
$mathjax
$darkmode
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/brands.min.css" integrity="sha512-9YHSK59/rjvhtDcY/b+4rdnl0V4LPDWdkKceBl8ZLF5TB6745ml1AfluEU6dFWqwDw9lPvnauxFgpKvJqp7jiQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<!-- https://tholman.com/github-corners/ -->
<a href="https://github.com/DigitalLibertiesLab/libdpf" class="github-corner" title="View source on GitHub" target="_blank">
<svg viewBox="0 0 250 250" width="40" height="40" style="opacity: 0.8; position: absolute; top: 0; border: 0; right: 0; z-index: 99;" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr id="projectrow">
<!--BEGIN PROJECT_LOGO-->
<td colspan="3"><img alt="libdpf++" src="$relpath^$projectlogo" width="330px" height="116px" /></td>
<!--END PROJECT_LOGO-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<tr><td colspan="2">$searchbox</td></tr>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

0
doc/libdpf.bib Normal file
View file

269
doc/libdpf.md Normal file
View file

@ -0,0 +1,269 @@
\mainpage notitle
<a id="mainpage"/>
[TOC]
![](libdpf-tagline.png)
%LIBDPF_INCLUDE_GETTING_STARTED_INTRODUCTION%
- - -
<div style="float:left; color:#999;">&larr;&nbsp;Prev</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref basics)</div>
<!-- PAGE SEPARATOR -->
\page basics DPF Basics
[TOC]
%LIBDPF_INCLUDE_GETTING_DPF_BASICS%
- - -
<div style="float:left; color:#999;">[&larr;&nbsp;Prev](mainpage)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref getting_started)</div>
<!-- PAGE SEPARATOR -->
\page getting_started Getting Started
[TOC]
The getting started guide consists of the following subsections.
- \subpage input_types
- \subpage output_types
- \subpage evaluation
- \subpage iterables
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref mainpage)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref input_types)</div>
<!-- PAGE SEPARATOR -->
\page input_types Input Types
[TOC]
%LIBDPF_INCLUDE_GETTING_STARTED_INPUT_TYPES%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref getting_started)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref output_types)</div>
<!-- PAGE SEPARATOR -->
\page output_types Output Types
[TOC]
%LIBDPF_INCLUDE_GETTING_STARTED_OUTPUT_TYPES%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref input_types)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref evaluation)</div>
<!-- PAGE SEPARATOR -->
\page evaluation Evaluating DPFs
[TOC]
%LIBDPF_INCLUDE_GETTING_STARTED_EVALUATION%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref output_types)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref iterables)</div>
<!-- PAGE SEPARATOR -->
\page iterables Iterables
[TOC]
%LIBDPF_INCLUDE_GETTING_STARTED_ITERABLES%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref evaluation)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref pir)</div>
<!-- PAGE SEPARATOR -->
\page pir PIR
[TOC]
The getting started guide consists of the following subsections.
- \subpage pir1
- \subpage pir2
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref iterable)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref pir1)</div>
\page pir1 PIR1
[TOC]
%LIBDPF_INCLUDE_PIR_PIR1%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref evaluation)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref miscellany)</div>
\page miscellany Miscellany
[TOC]
The miscellany page consists of the following subpages.
- \subpage bugs
- \subpage changes
- \subpage todo
- \subpage submodules
- \subpage authors
- \subpage license
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref pir)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref bugs)</div>
<!-- PAGE SEPARATOR -->
\page bugs Bugs
[TOC]
%LIBDPF_INCLUDE_MISC_BUGS%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref miscellany)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref changes)</div>
<!-- PAGE SEPARATOR -->
\page changes CHANGELOG
[TOC]
%LIBDPF_INCLUDE_MISC_CHANGES%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref bugs)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref todo)</div>
<!-- PAGE SEPARATOR -->
\page todo TODO list
[TOC]
%LIBDPF_INCLUDE_MISC_TODO%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref changes)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref submodules)</div>
<!-- PAGE SEPARATOR -->
\page submodules Submodules
[TOC]
%LIBDPF_INCLUDE_MISC_SUBMODULES%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref todo)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref authors)</div>
<!-- PAGE SEPARATOR -->
\page authors Authors
[TOC]
%LIBDPF_INCLUDE_MISC_AUTHORS%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref submodules)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref license)</div>
<!-- PAGE SEPARATOR -->
\page license License
[TOC]
%LIBDPF_INCLUDE_MISC_LICENSE%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref authors)</div>
<div style="float:right;">[Next&nbsp;&rarr;](@ref listings)</a></div>
<!-- PAGE SEPARATOR -->
\page listings Code Listings
[TOC]
%LIBDPF_INCLUDE_CODE_LISTINGS%
- - -
<div style="float:left;">[&larr;&nbsp;Prev](@ref license)</div>
<div style="float:right; color:#999;">Next&nbsp;&rarr;</div>

20
doc/namespaces.dox Normal file
View file

@ -0,0 +1,20 @@
/// @namespace dpf
/// @brief the `::dpf` namespace
/// @details all components of `libdpf++` live in this namespace (or one of
/// the namespaces nested within it)
/// @namespace dpf::utils
/// @brief the `dpf::utils` namespace
/// @details this is where all the utils go
/// @namespace dpf::details
/// @brief the `dpf::details` namespace`
/// @details this is where details go
/// @namespace dpf::internal
/// @brief the `dpf::internal` namespace
/// @details this is where internal stuff goes
/// @namespace dpf::wildcards
/// @brief the `dpf::wildcards` namespace
/// @details this is where pre-define wildcards go

5
doc/pages/basics.md Normal file
View file

@ -0,0 +1,5 @@
<!-- # DPF Basics {#basics} -->
# Point functions {#point_functions}
# DPF Trees {#dpf_trees}

80
doc/pages/evaluation.md Normal file
View file

@ -0,0 +1,80 @@
<!-- # Evaluating DPFs {#evalaution} -->
Once a `DPF` generated, the *eval_* * functions are used to evaluate differents inputs.
The appropriate function depends on your specific needs. If you only need the DPF's output
for a single input value, use `eval_point`. For evaluating a continuous range of inputs,
`eval_interval` is suitable. To analyze the DPF's behavior across its entire domain, use `eval_full`.
The code likely offers different implementations of memoization and output buffers, allowing you to
optimize for memory usage or execution speed depending on your needs.\n
Using a PRG while making a `DPF` allows the user to check how much it cost to manipulate the `DPF`s.
# Memoizers
The `memoizers` remembers the most used path while the DPF is being created. These are usefull functions
to improve the speed and the cost of execution.
**Code samples**\n
For instance, in the code below the utilization of `dpf::make_basic_path_memoizer` reduced by 10 the time of execution
compare to the code that is commented that doesn't use the `memoizers`.
<div class="tabbed">
- <b class="tab-title">memoizers.cpp</b> \include{cpp} evaluation/memoizers.cpp
</div>
# dpf::eval_point
This function evaluate a single input of a DPF. The XOR result of the `eval_point` for both shares will only be equal to 1 if it represents the correct input in both evaluations. As input arguments it uses the `share` and the input to evaluate (note: it can't be a wildcard, otherwise it will throw an error).\n
**See also**\n
PIR
**Pro tip**\n
Use the `dpf::pathmemoizer` for a faster execution.\n
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">eval_point.cpp</b> \include{cpp} evaluation/eval_point.cpp
</div>
# dpf::eval_interval
This function evaluates a contiguous range of inputs. As input arguments it uses the `share` generated by `make_dpf`,
`from` and `to` for the range of inputs to evaluate.\n
**See also**\n
PIR
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">eval_interval.cpp</b> \include{cpp} evaluation/eval_interval.cpp
</div>
# dpf::eval_full
This function evaluate all the passible inputs it only uses as argument the `share` of the `DPF` to evaluate.
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">eval_full.cpp</b> \include{cpp} evaluation/eval_full.cpp
</div>
# dpf::eval_sequence
This function evaluate a subset of inputs that is not contiguous (useful for a `DPF` made with `keyword`),
it uses as arguments the `share` generated by `make_dpf`, `from` and `to` for the subset of inputs to evaluate.\n
For a better utilization, you can use the `make_sequence_recipe` it takes as input a sorted list and returns a `recipe`.
The cost of creating is a little bit worse than just calling `eval_sequence`, however once the `recipe` created
`eval_sequence` is faster and has a better cost.
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">eval_sequence.cpp</b> \include{cpp} evaluation/eval_sequence.cpp
</div>
# Output buffers

236
doc/pages/input_types.md Normal file
View file

@ -0,0 +1,236 @@
<!-- # Input Types {#input_types} -->
An *input type* is the type used as the domain for the `x`-coordinate of a
DPF. `libdpf++` ships with native support for a number of convenient input
types, which are enumerated below. See also the [formal requirements](@ref custom_input_types)
for a type not listed below to be used as an input type.
Within the `libdpf++` source, the (typically deduced) template parameter
`typename InputT` indicates the input type of the DPF under consideration.
Moreover, the `dpf::dpf_key` class (and some others) publicly expose the
clause
```
using input_type = InputT;
```
providing an easy way to programmatically determine the input type.
# Integer scalar types
Any integer scalar type&mdash;that is, any type `T` such that
`std::numeric_limits<T>::is_integer == true`&mdash;may be used as an input
type. In general, you should always opt for the "shortest" such type that
suits your needs, as shorter bitlengths translate to smaller DPF keys and
faster evaluations thereof.
**Pro tip**\n
Prefer the use of [fixed width integer types](https://en.cppreference.com/w/cpp/types/integer)
over [fundamental integer types](https://en.cppreference.com/w/cpp/language/types)
for specifying input types. For example, use `uint16_t` in place of
`unsigned short`, or `int32_t` in place of `int`. Doing so improves
portability and makes it easier to keep track of the resulting DPF depth.
**See also**\n
The `dpf::modint` class template for custom-bitlength integer types that
allow tighter control over size of DPF keys.
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">integral_types.cpp</b> \include{cpp} input_types/integral_types.cpp
</div>
# Extended-precision integer scalar types
The extended-precision (`128`-bit) integer scalar types provided as
compiler extensions by most major C++ compilers (e.g., `__int128` and `unsigned __int128`), including `g++` and
`clang++` when compiling for `64`-bit targets. (As these types are not
defined in the C++17 standard, `std::numeric_limits` is not specialized
for them, so that `std::numeric_limits<__int128>::is_integer` returns
`false`.)
**Pro tip**\n
Use `simde_uint128` (provided courtesy of (SIMD Everywhere)[https://github.com/simd-everywhere/simde])
to declare such 128-bit integers in compiler-independent way.
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">extended_types.cpp</b> \include{cpp} input_types/extended_types.cpp
</div>
# dpf::modint<Nbits>
Arbitrary-, yet fixed-bitlength unsigned integer types. `dpf::modint` is a
lightweight class template that adapts one of the above-mentioned integer
types for arithmetic modulo `2^Nbits`, where `std::size_t Nbits` is the
template parameter. The specialization chooses an appropriate type for
the underlying integer and uses bitmasking to lazily reduce that integer
when the value is read (by anything other than a like-sized `modint`).
This ensures that arithmetic on `modint`s is just as fast as arithmetic on
the underlying integer type. Specializations with `Nbits` from `1` through
`256` inclusive are supported. Compound assignments (`+=`, `-=`, `*=`,
`&=`, `|=`, `^=`) modify the object even when the returned reference is
discarded.
**Pro tip**\n
Choose the smallest `Nbits` possible to get DPFs of the shortest length --
and with the fastest evaluations -- possible.
**Defined in**\n
@ref dpf/modint.hpp
**Code samples**\n
Here are some examples of arithmetic operations with `dpf::modint`:
<div class="tabbed">
- <b class="tab-title">modint.cpp</b> \include{cpp} input_types/modint.cpp
</div>
# dpf::bitstring<Nbits>
Arbitrary-, yet fixed- bitlength binary strings types. `dpf::bitstring` is
a class template that represents a binary string of any given length.
Compared with `dpf::modint`, a `dpf::bitstring` is well suited to cases
where inputs do not semantically stand for numerical values. For example,
the input may be a pseudorandom identifier or a cryptographic key. There
is no fixed limit on the acceptable bitlength for a `dpf::bitstring`.
In contrast with `dpf::modint`, which uses a (possibly extended-precision)
integer type for its internal representation, the `dpf::bitstring` class
template derives from `dpf::static_bit_array` and, therefore, provides a
wealth of methods and helpers for interacting with its individual bits.
**Pro tip**\n
As always, choose the smallest `Nbits` possible to get DPFs of the
shortest length -- and with the fastest evaluations -- possible.
**Defined in**\n
@ref dpf/bitstring.hpp
**See also**\n
`dpf::bit` and `dpf::static_bit_array`
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">bitstring.cpp</b> \include{cpp} input_types/bitstring.cpp
</div>
# dpf::keyword<Alphabet, N>
Fixed-length strings over restricted alphabets. `dpf::keyword` is an alias
for the class template `dpf::basic_fixed_length_string`, which represents
a string of length `N` consisting solely of letters from
`alphabet`, where `std::size_t N` and `static const char alphabet[]` are
template parameters. To a first approximation, the `dpf::keyword` class
template views each eligible string as an integer expressed in
radix-`std::strlen(alphabet)` and then stores the associated binary number as its
internal representation. This produces representations that are
*significantly* shorter than that of the associated C-string, especially
when `alphabet` comprises few elements.
For example
\code{cpp}
const char cstr[] = "7fffae02";
std::cout << (sizeof(cstr) - 1) * CHAR_BIT << "\n"; // prints 64
using kw = dpf::keyword<8, dpf::alphabets::hex>;
kw str = "7fffae02";
std::cout << dpf::utils::bitlength_of_v<kw> << "\n"; // prints 32
using kw2 = dpf::keyword<8, dpf::alphabets::alphanumeric>;
kw2 str2 = "7fffae02";
std::cout << dpf::utils::bitlength_of_v<kw2> << "\n"; // prints 48
inline constexpr char my_alphabet[] = "7fae02";
using kw3 = dpf::keyword<8, my_alphabet>;
kw3 str3 = "7fffae02";
std::cout << dpf::utils::bitlength_of_v<kw3> << "\n"; // prints 21
using kw4 = dpf::keyword<8, dpf::alphabets::lowercase_alpha>;
kw4 str4 = "7fffae02"; // error (disallowed chars)
\endcode
**Pro tip**\n
Strings implicitly padded to length `N` with "zeros"; i.e., with the first
letter in `alphabet`. To allow for strings of length *less than* `N`,
simply set ``alphabet[0]='\0'``.
**Defined in**\n
@ref dpf/keyword.hpp
**See also**\n
The `dpf::alphabets` namespace for a catalog of predefined alphabets.
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">keyword.cpp</b> \include{cpp} input_types/keyword.cpp
</div>
# dpf::xor_wrapper<T>
An element of `GF(2)^N` for `N=8*sizeof(T)`. `xor_wrapper` is a
lightweight class template that adapts "integer-like" types so that
arithmetic behaves like component-wise `GF(2)^N` arithmetic; that is,
(binary `+` and `-` both become `^`; binary `*` becomes `&`; unary `-`
becomes a nop). This is useful for cases where instances of the input type
are to be XOR-shared among two parties. `xor_wrapper` may be specialized
with any of the above-mentioned input types.
**Defined in**\n
@ref dpf/xor_wrapper.hpp
**Code samples**\n
<div class="tabbed">
- <b class="tab-title">xor_wrapper</b> \include{cpp} input_types/xor_wrapper.cpp
</div>
- - -
# Custom input type requirements {#custom_input_types}
A type can be a DPF input when the library can walk its bits and, for interval
or full-domain evaluation, order its values.
The walk uses `dpf::utils::msb_of<T>::value` as a mask. That mask must support
`mask & x` (the tested bit) and `mask >>= 1` (the next-lower bit). `dpf::utils::bitlength_of<T>`
is the number of steps; for a type that is not an integer it defaults to
`CHAR_BIT * sizeof(T)`, so specialize it when the meaningful width is smaller.
`dpf::utils::mod_pow_2<T>` extracts the low bits that select an output inside a
packed leaf (`offset_within_block`).
Interval and full-domain evaluation also need `operator>`, `operator<`, and
`std::numeric_limits<T>::min()` / `max()`. Sequence evaluation needs
`dpf::utils::countl_zero_symmetric_difference<T>` when the default, which
truncates to 64 bits, is not correct for `T`.
A complete example is `test/tests/helpers/custom_input_type.hpp`. The
comparison must be a real order:
\code{cpp}
bool operator>(input_type lhs, input_type rhs) { return lhs.i > rhs.i; }
\endcode
`msb_of` for a 32-bit payload looks like this. The mask type has to be the
type stored in `msb_of<T>::value`, and `mask & x` has to compile:
\code{cpp}
namespace dpf::utils {
template <> struct msb_of<input_type> {
static constexpr input_type value{int32_t{1} << 31};
};
template <> struct mod_pow_2<input_type> {
std::size_t operator()(input_type val, std::size_t n) const noexcept {
if (n == 0) return 0;
return static_cast<std::size_t>(static_cast<uint32_t>(val.i) & ((std::size_t{1} << n) - 1));
}
};
}
\endcode

33
doc/pages/introduction.md Normal file
View file

@ -0,0 +1,33 @@
# Introduction
## Synopsis {#synopsis}
`libdpf++` is a fast and extensible, header-only C++17 implementation of
`(2,2)-`*distributed point functions* (or *DPFs*). In addition to core DPF
functionality, `libdpf++` provides a plethora of data structures, helper
functions, and syntactic sugar designed to facilitate seamless integration
into higher-level cryptographic protocols and primitives. With its focus on
speed and ease of use, `libdpf++` is an ideal building block for implementing
private information retrieval (PIR), secure multiparty computation (MPC),
zero-knowledge arguments, anonymous messaging, and more.
## Features {#features}
- <i class="fa-solid fa-right-from-bracket"></i> input types
- <i class="fa-solid fa-right-to-bracket"></i> output types
- <i class="fa-solid fa-shuffle"></i> wildcards
- <i class="fa-brands fa-pagelines"></i> multiple leaves
- <i class="fa-solid fa-ellipsis"></i> evaluation types
- <i class="fa-solid fa-memory"></i> memoizers
- <i class="fa-solid fa-file-lines"></i> json serialization
- <i class="fa-solid fa-route"></i> asynchronous I/O
## Credits {#credits}
- Adithya Vadapalli (IIT Kanpur)
- Kyle Storrier (UCalgary)
- Allan Lyons (UCalgary)
## Disclaimer {#disclaimer}
We bet you $50 that there is at least one security bug in this code base. If you are you use this code, it is on you to verify that bug does not cross any of the same code paths as you.

13
doc/pages/iterables.md Normal file
View file

@ -0,0 +1,13 @@
<!-- # Iterables {#iterables} -->
# dpf::setbit_index_iterable
# dpf::subsequence_iterable
# dpf::subinterval_iterable
# dpf::zip_iterable
# dpf::parallel_bit_iterable
# dpf::advice_bit_iterable

122
doc/pages/listings.md Normal file
View file

@ -0,0 +1,122 @@
<!-- # Code Listings {#listings} -->
- \subpage input_type_examples
- \subpage output_type_examples
- \subpage evaluation_examples
- \subpage iteratable_examples
\page input_type_examples input_types
- \subpage input_types_2integral_types_8cpp
- \subpage input_types_2extended_types_8cpp
- \subpage input_types_2modint_8cpp
- \subpage input_types_2bitstring_8cpp
- \subpage input_types_2keyword_8cpp
- \subpage input_types_2xor_wrapper_8cpp
- \subpage input_types_2custom_8cpp
\page "input_types_2integral_types_8cpp" input_types/integral_types.cpp
\include{cpp} input_types/integral_types.cpp
\page "input_types_2extended_types_8cpp" input_types/extended_types.cpp
\include{cpp} input_types/extended_types.cpp
\page "input_types_2modint_8cpp" input_types/modint.cpp
\include{cpp} input_types/modint.cpp
\page "input_types_2bitstring_8cpp" input_types/bitstring.cpp
\include{cpp} input_types/bitstring.cpp
\page "input_types_2keyword_8cpp" input_types/keyword.cpp
\include{cpp} input_types/keyword.cpp
\page "input_types_2xor_wrapper_8cpp" input_types/xor_wrapper.cpp
\include{cpp} input_types/xor_wrapper.cpp
\page "input_types_2custom_8cpp" input_types/custom.cpp
\include{cpp} input_types/custom.cpp
\page output_type_examples output_types
- \subpage output_types_2integral_types_8cpp
- \subpage output_types_2extended_types_8cpp
- \subpage output_types_2bit_8cpp
- \subpage output_types_2bitstring_8cpp
- \subpage output_types_2wildcard_8cpp
- \subpage output_types_2xor_wrapper_8cpp
- \subpage output_types_2custom_8cpp
\page "output_types_2integral_types_8cpp" output_types/integral_types.cpp
\include{cpp} output_types/integral_types.cpp
\page "output_types_2extended_types_8cpp" output_types/extended_types.cpp
\include{cpp} output_types/extended_types.cpp
\page "output_types_2bit_8cpp" output_types/bit.cpp
\include{cpp} output_types/bit.cpp
\page "output_types_2bitstring_8cpp" output_types/bitstring.cpp
\include{cpp} output_types/bitstring.cpp
\page "output_types_2wildcard_8cpp" output_types/wildcard.cpp
\include{cpp} output_types/wildcard.cpp
\page "output_types_2xor_wrapper_8cpp" output_types/xor_wrapper.cpp
\include{cpp} output_types/xor_wrapper.cpp
\page "output_types_2custom_8cpp" output_types/custom.cpp
\include{cpp} output_types/custom.cpp
\page evaluation_examples evaluation
- \subpage evaluation_2eval_point_8cpp
- \subpage evaluation_2eval_interval_8cpp
- \subpage evaluation_2eval_full_8cpp
- \subpage evaluation_2eval_sequence_8cpp
- \subpage evaluation_2memoizers_8cpp
- \subpage evaluation_2output_buffers_8cpp
\page "evaluation_2eval_point_8cpp" evaluation/eval_point.cpp
\include{cpp} evaluation/eval_point.cpp
\page "evaluation_2eval_interval_8cpp" evaluation/eval_interval.cpp
\include{cpp} evaluation/eval_interval.cpp
\page "evaluation_2eval_full_8cpp" evaluation/eval_full.cpp
\include{cpp} evaluation/eval_full.cpp
\page "evaluation_2eval_sequence_8cpp" evaluation/eval_sequence.cpp
\include{cpp} evaluation/eval_sequence.cpp
\page "evaluation_2memoizers_8cpp" evaluation/memoizers.cpp
\include{cpp} evaluation/memoizers.cpp
\page "evaluation_2output_buffers_8cpp" evaluation/output_buffers.cpp
\include{cpp} evaluation/output_buffers.cpp
\page iteratable_examples iterables
- \subpage iterables_2setbit_index_iterable_8cpp
- \subpage iterables_2advice_bit_iterable_8cpp
- \subpage iterables_2parallel_bit_iterable_8cpp
- \subpage iterables_2subinterval_iterable_8cpp
- \subpage iterables_2subsequence_iterable_8cpp
- \subpage iterables_2zip_iterable_8cpp
\page "iterables_2setbit_index_iterable_8cpp" iterables/setbit_index_iterable.cpp
\include{cpp} iterables/setbit_index_iterable.cpp
\page "iterables_2advice_bit_iterable_8cpp" iterables/advice_bit_iterable.cpp
\include{cpp} iterables/advice_bit_iterable.cpp
\page "iterables_2parallel_bit_iterable_8cpp" iterables/parallel_bit_iterable.cpp
\include{cpp} iterables/parallel_bit_iterable.cpp
\page "iterables_2subinterval_iterable_8cpp" iterables/subinterval_iterable.cpp
\include{cpp} iterables/subinterval_iterable.cpp
\page "iterables_2subsequence_iterable_8cpp" iterables/subsequence_iterable.cpp
\include{cpp} iterables/subsequence_iterable.cpp
\page "iterables_2zip_iterable_8cpp" iterables/zip_iterable.cpp
\include{cpp} iterables/zip_iterable.cpp

90
doc/pages/output_types.md Normal file
View file

@ -0,0 +1,90 @@
<!-- # Output Types {#output_types} -->
An output type is the group element stored at the programmed input. Every
output of one DPF must have the same `dpf::utils::bitlength_of_output` width,
and each output type must be trivially copyable and standard layout. Leaf
addition and subtraction are the group operation; leaf multiplication scales
a leaf by one output element (used by wildcard Beaver triples and inner
products).
# Integer scalar types
Fixed-width integers (`uint32_t`, `int64_t`, and the other `psnip` widths)
are an additive group. Leaves use SIMD add, subtract, and multiply, including
`char`, `long long`, `char16_t`, `char32_t`, and `wchar_t` at 8, 16, 32, and
64 bits. `bool` is an 8-bit integer, not a packed bit. Use `dpf::bit` for one
bit.
`dpf::modint<N>` is an output as well as an input. Packed leaves add the
underlying word; values wider than one AES block add with `operator+`.
# Secret shares {#secret_shares}
`dpf::additive_share<T, Party>` and `dpf::subtractive_share<T, Party>` are
layout-identical wrappers around a number-like `T` (`Party` is `0` or `1`).
Reconstruction is `share0 + share1` for additive shares and `share0 - share1`
for subtractive shares. Creating from a plaintext puts the value on party 0
and zero on party 1.
Leaf evaluation returns subtractive shares of the payload. Comparison
(`lt`/`leq`/`gt`/`geq`) returns additive shares. Public plaintexts absorb on
party 0 only. Mixing additive and subtractive shares at the same party flips
the differing-scheme operand on party 1 so the opened secret stays correct.
Use `raw()` / `from_raw` / `retag` for intentional bit-level escapes.
`make_dpf` returns a `party_key` pair so each party's eval result is typed.
Pass plaintext domain points and payloads; use `reconstruct` (or `raw()`)
when you already hold shares.
# Extended-precision integer scalar types
`simde_int128`, `simde_uint128`, `uint128_t`, and `uint256_t` are additive.
Their leaf arithmetic is ordinary addition of those integers.
# dpf::bit
A one-bit output. The group is XOR: `operator+` and `operator-` are both XOR,
and a leaf multiply is AND with an all-zero or all-one mask. Many `dpf::bit`
outputs are packed into each leaf.
# dpf::bitstring<Nbits>
A fixed string of bits in the XOR group. `operator+`, `operator-`, and leaf
addition are XOR. The leftmost character of a literal or of `to_string` is
the most significant bit, matching `0b` notation. Bits above `Nbits` are not
part of the value.
# dpf::wildcard<T>
`dpf::wildcard_value<T>` is a placeholder. The leaf group is the group of
`T`. The value can be filled in later; until then evaluation of that output
throws. `operator()` accepts both lvalues and rvalues.
`float` and `double` wildcards are bitwise, not IEEE arithmetic. Leaf
addition is XOR of the representation and leaf scaling is AND, which is an
exact group. It is not floating-point addition.
# dpf::xor_wrapper<T>
An element of `GF(2)^n` for `n = 8 * sizeof(T)`, or `n = N` for
`dpf::xint<N>`. `operator+` and `operator-` are XOR, `operator*` is AND, and
`++` / `--` flip the low bit (the same XOR with 1). Leaf addition is XOR and
leaf scaling is AND.
# Custom output type requirements {#custom_output_types}
Specialize `dpf::leaf_arithmetic::add_t`, `subtract_t`, and `multiply_t` for
the exterior node type (`simde__m128i` for the default AES PRG, and
`simde__m256i` when that node is used). Each functor's call operator receives
two nodes for addition and subtraction, or a node and one output value for
multiplication, and returns a node.
When the output is larger than one node, the leaf is
`std::array<node, block_length>`. That path uses `operator+` and `operator-`
on the output type when those expressions are valid, and otherwise XORs the
blocks. Prefer an explicit specialization when the group is not
component-wise `+` of one output object.
Outputs must be trivially copyable and standard layout. `dpf::utils::make_from_integral_value<T>`
should build `T` from the integer `1` when tests or `make_default` need a
nonzero payload. See `test/tests/helpers/custom_output_type_small.hpp`.

180
doc/stylesheet.css Normal file
View file

@ -0,0 +1,180 @@
.github-corner svg {
fill: var(--primary-light-color);
color: var(--page-background-color);
width: 96px;
height: 96px;
}
@media screen and (max-width: 767px) {
.github-corner svg {
width: 72px;
height: 72px;
}
#projectnumber {
margin-right: 22px;
}
}
.alter-theme-button {
display: inline-block;
cursor: pointer;
background: var(--primary-color);
color: var(--page-background-color) !important;
border-radius: var(--border-radius-medium);
padding: var(--spacing-small) var(--spacing-medium);
text-decoration: none;
}
.alter-theme-button:hover {
background: var(--primary-dark-color);
}
html.dark-mode .darkmode_inverted_image img, /* < doxygen 1.9.3 */
html.dark-mode .darkmode_inverted_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ {
filter: brightness(89%) hue-rotate(180deg) invert();
}
.bordered_image {
border-radius: var(--border-radius-small);
border: 1px solid var(--separator-color);
display: inline-block;
overflow: hidden;
}
html.dark-mode .bordered_image img, /* < doxygen 1.9.3 */
html.dark-mode .bordered_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ {
border-radius: var(--border-radius-small);
}
.title_screenshot {
filter: drop-shadow(0px 3px 10px rgba(0,0,0,0.22));
max-width: 500px;
margin: var(--spacing-large) 0;
}
.title_screenshot .caption {
display: none;
}
.bugs\.html::before {
content: "\f188";
}
.changes\.html::before {
content: "\f1da";
}
.submodules\.html::before {
content: "\f1b3";
}
.todo\.html::before {
content: "\f46c";;
}
.authors\.html::before {
content: "\f0c0";
}
.license\.html::before {
content: "\f0e3";
}
.index\.html\:synopsis::before {
content: "\f52b";
}
.index\.html\:features::before {
content: "\f5a2";
}
.index\.html\:credits::before {
content: "\e1a8";
}
.index\.html\:disclaimer::before {
content: "\f3ed";
}
.basics\.html\:point_functions::before {
content: "\e4bd";
}
.basics\.html\:dpf_trees::before {
content: "\f1bb";
}
/*
point function: \e4bd
dpf trees: \e4bd
*/
.basics\.html\:point_functions::before,
.basics\.html\:dpf_trees::before,
.index\.html\:synopsis::before,
.index\.html\:features::before,
.index\.html\:credits::before,
.index\.html\:disclaimer::before,
.bugs\.html::before,
.changes\.html::before, .submodules\.html::before, .todo\.html::before,
.authors\.html::before, .license\.html::before,
.input_types_2integral_types_8cpp\.html::before,
.input_types_2extended_types_8cpp\.html::before,
.input_types_2modint_8cpp\.html::before,
.input_types_2bitstring_8cpp\.html::before,
.input_types_2keyword_8cpp\.html::before,
.input_types_2xor_wrapper_8cpp\.html::before,
.input_types_2custom_8cpp\.html::before,
.output_types_2integral_types_8cpp\.html::before,
.output_types_2extended_types_8cpp\.html::before,
.output_types_2bit_8cpp\.html::before,
.output_types_2bitstring_8cpp\.html::before,
.output_types_2wildcard_8cpp\.html::before,
.output_types_2xor_wrapper_8cpp\.html::before,
.output_types_2custom_8cpp\.html::before,
.evaluation_2eval_point_8cpp\.html::before,
.evaluation_2eval_interval_8cpp\.html::before,
.evaluation_2eval_full_8cpp\.html::before,
.evaluation_2eval_sequence_8cpp\.html::before,
.evaluation_2memoizers_8cpp\.html::before,
.evaluation_2output_buffers_8cpp\.html::before,
.iterables_2setbit_index_iterable_8cpp\.html::before,
.iterables_2advice_bit_iterable_8cpp\.html::before,
.iterables_2parallel_bit_iterable_8cpp\.html::before,
.iterables_2subinterval_iterable_8cpp\.html::before,
.iterables_2subsequence_iterable_8cpp\.html::before,
.iterables_2zip_iterable_8cpp\.html::before {
display: inline-block;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
scale: 0.9;
font: var(--fa-font-solid);
margin-left:-1em;
width: 1.5em;
text-align: center;
}
.input_types_2integral_types_8cpp\.html::before,
.input_types_2extended_types_8cpp\.html::before,
.input_types_2modint_8cpp\.html::before,
.input_types_2bitstring_8cpp\.html::before,
.input_types_2keyword_8cpp\.html::before,
.input_types_2xor_wrapper_8cpp\.html::before,
.input_types_2custom_8cpp\.html::before,
.output_types_2integral_types_8cpp\.html::before,
.output_types_2extended_types_8cpp\.html::before,
.output_types_2bit_8cpp\.html::before,
.output_types_2bitstring_8cpp\.html::before,
.output_types_2wildcard_8cpp\.html::before,
.output_types_2xor_wrapper_8cpp\.html::before,
.output_types_2custom_8cpp\.html::before,
.evaluation_2eval_point_8cpp\.html::before,
.evaluation_2eval_interval_8cpp\.html::before,
.evaluation_2eval_full_8cpp\.html::before,
.evaluation_2eval_sequence_8cpp\.html::before,
.evaluation_2memoizers_8cpp\.html::before,
.evaluation_2output_buffers_8cpp\.html::before,
.iterables_2setbit_index_iterable_8cpp\.html::before,
.iterables_2advice_bit_iterable_8cpp\.html::before,
.iterables_2parallel_bit_iterable_8cpp\.html::before,
.iterables_2subinterval_iterable_8cpp\.html::before,
.iterables_2subsequence_iterable_8cpp\.html::before,
.iterables_2zip_iterable_8cpp\.html::before {
content: "\f1c9";
}

View file

@ -0,0 +1,170 @@
// Regression checks for the eval / memoizer / output-buffer audit fixes.
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <limits>
#include <vector>
#include "dpf.hpp"
static int fails = 0;
static void expect(bool ok, const char *what)
{
if (!ok)
{
std::fprintf(stderr, "FAIL: %s\n", what);
++fails;
}
}
static void test_wrap_interval()
{
using input_t = uint8_t;
using output_t = uint64_t;
const input_t alpha = 252;
const output_t beta = 0x1234567890abcdefULL;
auto [k0, k1] = dpf::make_dpf(input_t{alpha}, output_t{beta});
const input_t from = 250;
const input_t to = 5;
auto [buf0, it0] = dpf::eval_interval(k0, from, to);
auto [buf1, it1] = dpf::eval_interval(k1, from, to);
auto z0 = std::begin(it0);
auto z1 = std::begin(it1);
int seen = 0;
for (int step = 0; step < 16; ++step, ++z0, ++z1)
{
const input_t x = static_cast<input_t>(from + step);
if (z0 == std::end(it0))
break;
const auto p0 = dpf::eval_point(k0, x);
const auto p1 = dpf::eval_point(k1, x);
const output_t interval = static_cast<output_t>(*z1) - static_cast<output_t>(*z0);
const output_t point = static_cast<output_t>(*p1) - static_cast<output_t>(*p0);
const output_t want = (x == alpha) ? beta : output_t{0};
expect(interval == point && interval == want, "wrap interval matches point");
++seen;
}
expect(z0 == std::end(it0), "wrap iterable consumed");
expect(seen == 12, "wrap covers 250..255,0..5");
}
static void test_uint64_suffix()
{
using input_t = uint64_t;
using output_t = uint64_t;
using key_t = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
input_t, output_t>;
const input_t alpha = std::numeric_limits<input_t>::max() - 1;
const output_t beta = 77;
auto [k0, k1] = dpf::make_dpf(input_t{alpha}, output_t{beta});
input_t flipped_to = std::numeric_limits<input_t>::max();
dpf::utils::flip_msb_if_signed_integral(flipped_to);
const auto to_node = dpf::utils::get_to_node<key_t>(flipped_to);
expect(to_node == (input_t{1} << 63), "uint64 max exclusive leaf is 2^63");
const input_t from = std::numeric_limits<input_t>::max() - 3;
const input_t to = std::numeric_limits<input_t>::max();
auto [buf0, it0] = dpf::eval_interval(k0, from, to);
auto [buf1, it1] = dpf::eval_interval(k1, from, to);
auto z0 = std::begin(it0);
auto z1 = std::begin(it1);
int seen = 0;
for (input_t x = from;; ++x, ++z0, ++z1)
{
expect(z0 != std::end(it0), "uint64 suffix still has outputs");
const auto p0 = dpf::eval_point(k0, x);
const auto p1 = dpf::eval_point(k1, x);
const output_t interval = static_cast<output_t>(*z1) - static_cast<output_t>(*z0);
const output_t point = static_cast<output_t>(*p1) - static_cast<output_t>(*p0);
const output_t want = (x == alpha) ? beta : output_t{0};
expect(interval == point && interval == want, "uint64 suffix matches point");
++seen;
if (x == to)
break;
}
++z0;
expect(z0 == std::end(it0), "uint64 suffix iterable consumed");
expect(seen == 4, "uint64 suffix length");
}
static void test_narrow_inner_product()
{
using input_t = uint8_t;
using output_t = uint8_t;
const input_t alpha = 30;
const output_t beta = 7;
auto [k0, k1] = dpf::make_dpf(input_t{alpha}, output_t{beta});
uint64_t w[256];
for (int i = 0; i < 256; ++i)
w[i] = static_cast<uint64_t>(i * 3 + 1);
const input_t from = 0;
const input_t to = 255;
auto memo0 = dpf::make_basic_full_memoizer(k0);
auto memo1 = dpf::make_basic_full_memoizer(k1);
auto ip0 = dpf::eval_inner_product(k0, from, to, w, memo0);
auto ip1 = dpf::eval_inner_product(k1, from, to, w, memo1);
// Shares live in the output group, so a uint8 dot product is mod 256.
// alpha sits in lane 14 of its leaf, past the old 8-lane read.
const uint64_t got = static_cast<uint64_t>(ip1) - static_cast<uint64_t>(ip0);
const uint64_t want = (static_cast<uint64_t>(beta) * w[alpha]) & 0xffu;
expect(got == want, "uint8 inner product (16 lanes per leaf)");
}
static void test_empty_sequence()
{
using input_t = uint8_t;
using output_t = uint64_t;
auto [k0, k1] = dpf::make_dpf(input_t{3}, output_t{1});
std::vector<input_t> pts;
auto [buf, it] = dpf::eval_sequence(k0, pts.begin(), pts.end(),
dpf::return_output_only_tag_{});
expect(std::begin(it) == std::end(it), "empty output-only sequence");
auto [bbuf, bit] = dpf::eval_sequence_breadth_first(k0, pts.begin(), pts.end());
expect(std::begin(bit) == std::end(bit), "empty breadth-first sequence");
auto recipe = dpf::make_sequence_recipe<decltype(k0)>(pts.begin(), pts.end());
expect(recipe.num_leaf_nodes() == 0, "empty recipe has no leaves");
auto [rbuf, rit] = dpf::eval_sequence(k1, recipe, dpf::return_output_only_tag_{});
expect(std::begin(rit) == std::end(rit), "empty recipe eval");
(void)buf;
(void)bbuf;
(void)rbuf;
}
static void test_path_high_water()
{
using input_t = uint16_t;
using output_t = uint64_t;
const input_t alpha = 1000;
const output_t beta = 42;
auto [k0, k1] = dpf::make_dpf(input_t{alpha}, output_t{beta});
auto path = dpf::make_basic_path_memoizer(k0);
input_t tx = alpha;
dpf::utils::flip_msb_if_signed_integral(tx);
dpf::detail::ensure_level(k0, tx, path, 2);
const auto partial = dpf::eval_point(k0, alpha, path);
const auto fresh = dpf::eval_point(k0, alpha);
const auto other = dpf::eval_point(k1, alpha);
const output_t got = static_cast<output_t>(*other) - static_cast<output_t>(*partial);
const output_t want = static_cast<output_t>(*other) - static_cast<output_t>(*fresh);
expect(got == beta && want == beta, "point eval resumes a partial path");
}
int main()
{
test_wrap_interval();
test_uint64_suffix();
test_narrow_inner_product();
test_empty_sequence();
test_path_high_water();
if (fails != 0)
{
std::fprintf(stderr, "%d check(s) failed\n", fails);
return EXIT_FAILURE;
}
std::printf("eval audit fixes ok\n");
return EXIT_SUCCESS;
}

367
examples/eval_opt_smoke.cpp Normal file
View file

@ -0,0 +1,367 @@
// Smoke test for the interval-eval optimizations: pipelined interior
// eval01 / eval01_x4, round-major and x4/x8 exterior AES, fused dual-output
// leaf pass, uninitialized output buffers.
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include "dpf.hpp"
static int fails = 0;
static void expect(bool ok, const char *what)
{
if (!ok)
{
std::fprintf(stderr, "FAIL: %s\n", what);
++fails;
}
}
static bool m128_eq(simde__m128i a, simde__m128i b)
{
return std::memcmp(&a, &b, sizeof(a)) == 0;
}
static void test_aes_batch()
{
using prg = dpf::prg::aes128;
simde__m128i seed = simde_mm_set_epi64x(
static_cast<int64_t>(0xfedcba9876543210ULL),
static_cast<int64_t>(0x0123456789abcdefULL));
auto a0 = prg::eval(seed, 0);
auto a1 = prg::eval(seed, 1);
auto a2 = prg::eval(seed, 2);
auto a3 = prg::eval(seed, 3);
auto kids = prg::eval01(seed);
expect(m128_eq(kids[0], a0), "eval01[0] == eval(seed, 0)");
expect(m128_eq(kids[1], a1), "eval01[1] == eval(seed, 1)");
simde__m128i buf2[2];
prg::eval(seed, buf2, 2, 0);
expect(m128_eq(buf2[0], a0), "batch count=2 pos=0 [0]");
expect(m128_eq(buf2[1], a1), "batch count=2 pos=0 [1]");
simde__m128i buf1[1];
prg::eval(seed, buf1, 1, 3);
expect(m128_eq(buf1[0], a3), "batch count=1 pos=3");
simde__m128i buf4[4];
prg::eval(seed, buf4, 4, 0);
expect(m128_eq(buf4[0], a0) && m128_eq(buf4[1], a1)
&& m128_eq(buf4[2], a2) && m128_eq(buf4[3], a3),
"round-major batch count=4");
simde__m128i buf2p[2];
prg::eval(seed, buf2p, 2, 2);
expect(m128_eq(buf2p[0], a2) && m128_eq(buf2p[1], a3),
"batch count=2 pos=2");
simde__m128i seeds[4];
simde__m128i left[4], right[4];
for (int i = 0; i < 4; ++i)
{
seeds[i] = simde_mm_xor_si128(seed, simde_mm_set_epi64x(0, i + 1));
}
prg::eval01_x4(seeds, left, right);
for (int i = 0; i < 4; ++i)
{
auto kids = prg::eval01(seeds[i]);
expect(m128_eq(left[i], kids[0]) && m128_eq(right[i], kids[1]),
"eval01_x4 matches eval01");
}
simde__m128i x4[4];
prg::eval_x4(seeds, x4, 3);
for (int i = 0; i < 4; ++i)
{
expect(m128_eq(x4[i], prg::eval(seeds[i], 3)),
"eval_x4 matches eval");
}
simde__m128i seeds8[8];
simde__m128i x8[8];
for (int i = 0; i < 8; ++i)
{
seeds8[i] = simde_mm_xor_si128(seed, simde_mm_set_epi64x(i + 9, i + 1));
}
prg::eval_x8(seeds8, x8, 0);
for (int i = 0; i < 8; ++i)
{
expect(m128_eq(x8[i], prg::eval(seeds8[i], 0)),
"eval_x8 matches eval");
}
}
static void test_dual_interval()
{
using input_t = dpf::modint<8>;
using add_t = psnip_uint64_t;
using xor_t = dpf::xor_wrapper<psnip_uint64_t>;
using dpf_t = dpf::utils::dpf_type_t<
dpf::prg::aes128, dpf::prg::aes128, input_t, add_t, xor_t>;
const uint64_t alpha = 37;
const uint64_t beta_add = 0x1111111111111111ULL;
const uint64_t beta_xor = 0xaaaaaaaaaaaaaaaaULL;
auto args = dpf::make_dpfargs(
input_t{static_cast<typename input_t::integral_type>(alpha)},
static_cast<add_t>(beta_add),
xor_t{static_cast<psnip_uint64_t>(beta_xor)});
auto [k0, k1] = dpf::make_dpf(std::move(args));
auto from = std::numeric_limits<input_t>::min();
auto to = std::numeric_limits<input_t>::max();
auto add0 = dpf::make_output_buffer_for_full<0>(k0);
auto xor0 = dpf::make_output_buffer_for_full<1>(k0);
auto add1 = dpf::make_output_buffer_for_full<0>(k1);
auto xor1 = dpf::make_output_buffer_for_full<1>(k1);
auto memo0 = dpf::make_basic_full_memoizer(k0);
auto memo1 = dpf::make_basic_full_memoizer(k1);
auto bufs0 = std::forward_as_tuple(add0, xor0);
auto bufs1 = std::forward_as_tuple(add1, xor1);
dpf::eval_interval<0, 1>(k0, from, to, bufs0, memo0);
dpf::eval_interval<0, 1>(k1, from, to, bufs1, memo1);
const int n = 1 << 8;
int add_hits = 0, xor_hits = 0, add_miss = 0, xor_miss = 0;
for (int x = 0; x < n; ++x)
{
auto in = input_t{static_cast<typename input_t::integral_type>(x)};
uint64_t s_add = static_cast<uint64_t>(add1[x])
- static_cast<uint64_t>(add0[x]);
uint64_t s_xor = static_cast<uint64_t>(static_cast<psnip_uint64_t>(xor_t(xor0[x])))
^ static_cast<uint64_t>(static_cast<psnip_uint64_t>(xor_t(xor1[x])));
auto p0 = dpf::eval_point<0>(k0, in);
auto p1 = dpf::eval_point<0>(k1, in);
uint64_t point_add = static_cast<uint64_t>(*p1) - static_cast<uint64_t>(*p0);
if (x == static_cast<int>(alpha))
{
if (s_add == beta_add) ++add_hits; else ++add_miss;
if (s_xor == beta_xor) ++xor_hits; else ++xor_miss;
expect(point_add == beta_add, "eval_point add at alpha");
}
else
{
if (s_add == 0) ++add_hits; else ++add_miss;
if (s_xor == 0) ++xor_hits; else ++xor_miss;
expect(point_add == 0, "eval_point add off alpha");
}
expect(s_add == point_add, "interval add matches eval_point");
}
expect(add_miss == 0 && add_hits == n, "dual-output additive reconstruct");
expect(xor_miss == 0 && xor_hits == n, "dual-output xor reconstruct");
}
static void test_four_outputs_and_wrap()
{
using input_t = dpf::modint<8>;
using out_t = psnip_uint64_t;
auto args = dpf::make_dpfargs(
input_t{static_cast<typename input_t::integral_type>(5)},
static_cast<out_t>(1), static_cast<out_t>(2),
static_cast<out_t>(3), static_cast<out_t>(4));
auto [k0, k1] = dpf::make_dpf(std::move(args));
auto from = std::numeric_limits<input_t>::min();
auto to = std::numeric_limits<input_t>::max();
auto [bufs0, it0] = dpf::eval_interval<0, 1, 2, 3>(k0, from, to);
auto [bufs1, it1] = dpf::eval_interval<0, 1, 2, 3>(k1, from, to);
const uint64_t want[4] = {1, 2, 3, 4};
for (int i = 0; i < 4; ++i)
{
const auto & a = (i == 0) ? std::get<0>(bufs0)
: (i == 1) ? std::get<1>(bufs0)
: (i == 2) ? std::get<2>(bufs0) : std::get<3>(bufs0);
const auto & b = (i == 0) ? std::get<0>(bufs1)
: (i == 1) ? std::get<1>(bufs1)
: (i == 2) ? std::get<2>(bufs1) : std::get<3>(bufs1);
for (int x = 0; x < 256; ++x)
{
uint64_t s = static_cast<uint64_t>(b[x]) - static_cast<uint64_t>(a[x]);
uint64_t exp = (x == 5) ? want[i] : 0ULL;
if (s != exp)
{
expect(false, "4-output fused reconstruct");
return;
}
}
}
}
static void test_single_and_reuse()
{
using input_t = dpf::modint<8>;
using out_t = psnip_uint64_t;
auto args = dpf::make_dpfargs(
input_t{static_cast<typename input_t::integral_type>(11)},
static_cast<out_t>(7));
auto [k0, k1] = dpf::make_dpf(std::move(args));
auto from = std::numeric_limits<input_t>::min();
auto to = std::numeric_limits<input_t>::max();
auto buf0 = dpf::make_output_buffer_for_full<0>(k0);
auto buf1 = dpf::make_output_buffer_for_full<0>(k1);
auto memo0 = dpf::make_basic_full_memoizer(k0);
auto memo1 = dpf::make_basic_full_memoizer(k1);
dpf::eval_interval<0>(k0, from, to, buf0, memo0);
dpf::eval_interval<0>(k1, from, to, buf1, memo1);
// Reuse the same buffers / memoizer with a second key pair.
auto args2 = dpf::make_dpfargs(
input_t{static_cast<typename input_t::integral_type>(200)},
static_cast<out_t>(99));
auto [k2, k3] = dpf::make_dpf(std::move(args2));
dpf::eval_interval<0>(k2, from, to, buf0, memo0);
dpf::eval_interval<0>(k3, from, to, buf1, memo1);
for (int x = 0; x < 256; ++x)
{
uint64_t s = static_cast<uint64_t>(buf1[x]) - static_cast<uint64_t>(buf0[x]);
uint64_t want = (x == 200) ? 99ULL : 0ULL;
if (s != want)
{
expect(false, "reused buffer/memoizer reconstruct");
return;
}
}
}
static void test_partial_interval()
{
using input_t = dpf::modint<8>;
using out_t = psnip_uint64_t;
auto args = dpf::make_dpfargs(
input_t{static_cast<typename input_t::integral_type>(17)},
static_cast<out_t>(42));
auto [k0, k1] = dpf::make_dpf(std::move(args));
// 12 leaf nodes (24 outputs): hits eval_x8 then eval_x4. Size is a
// multiple of the 64-byte output_buffer alignment (ASan aligned_alloc).
auto from = input_t{static_cast<typename input_t::integral_type>(0)};
auto to = input_t{static_cast<typename input_t::integral_type>(23)};
auto [bufs0, it0] = dpf::eval_interval<0>(k0, from, to);
auto [bufs1, it1] = dpf::eval_interval<0>(k1, from, to);
(void)bufs0;
(void)bufs1;
auto z0 = std::begin(it0);
auto z1 = std::begin(it1);
auto e0 = std::end(it0);
for (int x = 0; z0 != e0; ++x, ++z0, ++z1)
{
uint64_t s = static_cast<uint64_t>(*z1) - static_cast<uint64_t>(*z0);
uint64_t want = (x == 17) ? 42ULL : 0ULL;
if (s != want)
{
expect(false, "partial interval reconstruct");
return;
}
auto in = input_t{static_cast<typename input_t::integral_type>(x)};
auto p0 = dpf::eval_point<0>(k0, in);
auto p1 = dpf::eval_point<0>(k1, in);
uint64_t point = static_cast<uint64_t>(*p1) - static_cast<uint64_t>(*p0);
expect(s == point, "partial interval matches eval_point");
}
}
static void test_inner_product()
{
using input_t = dpf::modint<8>;
using add_t = psnip_uint64_t;
using xor_t = dpf::xor_wrapper<psnip_uint64_t>;
const uint64_t alpha = 19;
const uint64_t beta_add = 7;
const uint64_t beta_xor = 0x5a5a5a5a5a5a5a5aULL;
auto args = dpf::make_dpfargs(
input_t{static_cast<typename input_t::integral_type>(alpha)},
static_cast<add_t>(beta_add),
xor_t{static_cast<psnip_uint64_t>(beta_xor)});
auto [k0, k1] = dpf::make_dpf(std::move(args));
auto from = std::numeric_limits<input_t>::min();
auto to = std::numeric_limits<input_t>::max();
const int n = 1 << 8;
uint64_t w_add[256];
uint64_t w_xor[256];
for (int i = 0; i < n; ++i)
{
w_add[i] = static_cast<uint64_t>(i * 3 + 1);
w_xor[i] = static_cast<uint64_t>(0x1111111111111111ULL * (i + 1));
}
auto add0 = dpf::make_output_buffer_for_full<0>(k0);
auto xor0 = dpf::make_output_buffer_for_full<1>(k0);
auto add1 = dpf::make_output_buffer_for_full<0>(k1);
auto xor1 = dpf::make_output_buffer_for_full<1>(k1);
auto memo0 = dpf::make_basic_full_memoizer(k0);
auto memo1 = dpf::make_basic_full_memoizer(k1);
auto bufs0 = std::forward_as_tuple(add0, xor0);
auto bufs1 = std::forward_as_tuple(add1, xor1);
dpf::eval_interval<0, 1>(k0, from, to, bufs0, memo0);
dpf::eval_interval<0, 1>(k1, from, to, bufs1, memo1);
uint64_t dot_add0 = 0, dot_add1 = 0, dot_xor0 = 0, dot_xor1 = 0;
for (int i = 0; i < n; ++i)
{
dot_add0 += static_cast<uint64_t>(add0[i]) * w_add[i];
dot_add1 += static_cast<uint64_t>(add1[i]) * w_add[i];
dot_xor0 ^= static_cast<uint64_t>(static_cast<psnip_uint64_t>(xor_t(xor0[i])))
& w_xor[i];
dot_xor1 ^= static_cast<uint64_t>(static_cast<psnip_uint64_t>(xor_t(xor1[i])))
& w_xor[i];
}
auto memo0b = dpf::make_basic_full_memoizer(k0);
auto memo1b = dpf::make_basic_full_memoizer(k1);
dpf::eval_prepare_interval(k0, from, to, memo0b);
dpf::eval_prepare_interval(k1, from, to, memo1b);
auto [ip_add0, ip_xor0] = dpf::eval_inner_product<0, 1>(
k0, from, to, std::forward_as_tuple(w_add, w_xor), memo0b);
auto [ip_add1, ip_xor1] = dpf::eval_inner_product<0, 1>(
k1, from, to, std::forward_as_tuple(w_add, w_xor), memo1b);
expect(static_cast<uint64_t>(ip_add0) == dot_add0, "inner product add p0");
expect(static_cast<uint64_t>(ip_add1) == dot_add1, "inner product add p1");
expect(static_cast<uint64_t>(static_cast<psnip_uint64_t>(xor_t(ip_xor0)))
== dot_xor0, "inner product xor p0");
expect(static_cast<uint64_t>(static_cast<psnip_uint64_t>(xor_t(ip_xor1)))
== dot_xor1, "inner product xor p1");
uint64_t recon_add = static_cast<uint64_t>(ip_add1)
- static_cast<uint64_t>(ip_add0);
uint64_t recon_xor = static_cast<uint64_t>(static_cast<psnip_uint64_t>(xor_t(ip_xor0)))
^ static_cast<uint64_t>(static_cast<psnip_uint64_t>(xor_t(ip_xor1)));
expect(recon_add == beta_add * w_add[static_cast<int>(alpha)],
"inner product reconstruct add");
expect(recon_xor == (beta_xor & w_xor[static_cast<int>(alpha)]),
"inner product reconstruct xor");
auto [ip_add0b, ip_add0c] = dpf::eval_full_inner_product<0, 0>(
k0, std::forward_as_tuple(w_add, w_add), memo0b);
expect(static_cast<uint64_t>(ip_add0b) == static_cast<uint64_t>(ip_add0),
"duplicate-output inner product");
expect(static_cast<uint64_t>(ip_add0c) == static_cast<uint64_t>(ip_add0),
"duplicate-output inner product match");
}
int main()
{
test_aes_batch();
test_dual_interval();
test_four_outputs_and_wrap();
test_single_and_reuse();
test_partial_interval();
test_inner_product();
if (fails)
{
std::fprintf(stderr, "%d check(s) failed\n", fails);
return 1;
}
std::puts("eval_opt_smoke: ok");
return 0;
}

View file

@ -0,0 +1,31 @@
#include <iostream>
#include "dpf.hpp"
int main(int arc, char * argv[])
{
uint16_t x = 42; // Input value
using prg = dpf::prg::counter_wrapper<dpf::prg::dummy>; // This is just to count the number of PRG invocations
auto before = prg::count(); // In order to show how much this program cost
auto [dpf0, dpf1] = dpf::make_dpf<prg>(x);
auto after = prg::count();
std::cout << "dpf::make_dpf used " << (after-before) << "\n";
before = prg::count();
auto [buf0, iter0] = dpf::eval_full(dpf0);
after = prg::count();
std::cout << "dpf::eval_full(dpf0) used " << (after-before) << "\n";
before = prg::count();
auto [buf1, iter1] = dpf::eval_full(dpf1);
after = prg::count();
std::cout << "dpf::eval_full(dpf1) used " << (after-before) << "\n";
// Retrieve the original input by iterating over the two buffers
for (size_t i = 0; i < buf0.size(); ++i) {
bool item1 = buf0[i];
bool item2 = buf1[i];
if (item1 ^ item2) std::cout << "The original input is: " << i << std::endl;
}
std::cout << "Total PRG invocation: " << prg::count() << "\n";
return 0;
}

View file

@ -0,0 +1,42 @@
#include <iostream>
#include "dpf.hpp"
int main(int arc, char * argv[])
{
uint16_t x = 42, y;
using prg = dpf::prg::counter_wrapper<dpf::prg::dummy>;
// Make the DPF
auto before = prg::count();
auto [dpf0, dpf1] = dpf::make_dpf<prg>(x);
auto after = prg::count();
std::cout << "dpf::make_dpf prg invocation: " << (after-before) << "\n";
// Evaluate the DPF by interval
before = prg::count();
int from = 0, to = 49;
auto [buf0, iter0] = dpf::eval_interval(dpf0, from, to);
auto [buf1, iter1] = dpf::eval_interval(dpf1, from, to);
after = prg::count();
std::cout << "dpf::eval_interval prg invocation: " << (after-before) << "\n";
// Retrieve the original input by iterating over the two buffers
std::vector<bool> result;
for (size_t i = from; i < to+1; ++i) {
bool item1 = buf0[i];
bool item2 = buf1[i];
result.push_back(item1 ^ item2);
if (item1 ^ item2) y=i;
}
// Print out the XOR interval
for (const auto& item : result) {
std::cout << static_cast<bool>(item);
}
std::cout << std::endl;
if (y == x) std::cout << "The orginal value is: " << x << std::endl;
else std::cout << "The evaluated inputs did not match the original value." << std::endl;
std::cout << "Total PRG invocation: " << prg::count() << std::endl;
return 0;
}

View file

@ -0,0 +1,17 @@
#include <iostream>
#include "dpf.hpp"
int main(int arc, char * argv[])
{
uint16_t x = 42;
auto [dpf0, dpf1] = dpf::make_dpf(x);
auto res = dpf::eval_point(dpf0, x);
std::cout << *dpf::eval_point(dpf0, 41) << " ^ " << *dpf::eval_point(dpf1, 41) << " = " << (*dpf::eval_point(dpf0, 41) ^ *dpf::eval_point(dpf1, 41)) << "\n"; // = 0
std::cout << *dpf::eval_point(dpf0, x) << " ^ " << *dpf::eval_point(dpf1, x) << " = " << (*dpf::eval_point(dpf0, x) ^ *dpf::eval_point(dpf1, x)) << "\n"; // = 1
std::cout << *dpf::eval_point(dpf0, 43) << " ^ " << *dpf::eval_point(dpf1, 43) << " = " << (*dpf::eval_point(dpf0, 43) ^ *dpf::eval_point(dpf1, 43)) << "\n"; // = 0
return 0;
}

View file

@ -0,0 +1,54 @@
#include <chrono>
#include <iostream>
#include "dpf.hpp"
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
int main(int argc, char * argv[])
{
using input_type = uint8_t;
using prg = dpf::prg::counter_wrapper<dpf::prg::dummy>;
constexpr int N = 50;
std::array<input_type, N> keys{};
for(int i=0; i<N; i++) keys[i] = i; // Create an array of keys
// eval_sequence with recipe
input_type x = 42;
auto [dpf0, dpf1] = dpf::make_dpf<prg>(x); // First DPF to be able to create the recipe
auto t1 = high_resolution_clock::now(); // To measure the time of execution
auto before = prg::count(); // To count the number of PRG invocations
auto recipe0 = dpf::make_sequence_recipe(dpf0, std::begin(keys), std::end(keys)); // Create a recipe
auto recipe1 = dpf::make_sequence_recipe(dpf1, std::begin(keys), std::end(keys)); // Create a recipe
for (int i=0; i<N; i++)
{
auto [dpf00, dpf11] = dpf::make_dpf<prg>(i); // Make 50 DPFs
dpf::eval_sequence(dpf0, recipe0); // Evaluate the DPFs with the recipe
dpf::eval_sequence(dpf1, recipe0); // Evaluate the DPFs with the recipe
}
auto after = prg::count(); // Count the number of PRG invocations
std::cout << "dpf::eval_sequence with recipe " << (after-before) << "\n";
// eval_sequence without the recipe
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "Time of execution: " << ms_double.count() << "ms\n";
auto t3 = high_resolution_clock::now();
before = prg::count();
for (int i=0; i<N; i++)
{
auto [dpf00, dpf11] = dpf::make_dpf<prg>(i);
dpf::eval_sequence(dpf00, std::begin(keys), std::end(keys));
dpf::eval_sequence(dpf11, std::begin(keys), std::end(keys));
}
after = prg::count();
std::cout << "dpf::eval_sequence used " << (after-before) << "\n";
auto t4 = high_resolution_clock::now();
duration<double, std::milli> ms_double2 = t4 - t3;
std::cout << "Time of execution with the memoizers: " << ms_double2.count() << "ms\n";
return 0;
}

View file

@ -0,0 +1,50 @@
#include <iostream>
#include <chrono>
#include "dpf.hpp"
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
int main(int arc, char * argv[])
{
// Making the DPF with an integer value
uint16_t x = 42;
using prg = dpf::prg::counter_wrapper<dpf::prg::dummy>;
auto [dpf0, dpf1] = dpf::make_dpf<prg>(x);
// Evaluating the DPF and counting how much it cost without memoizers
auto t1 = high_resolution_clock::now();
auto before = prg::count();
for (int i = 0; i<1024*1024; i++)
{
dpf::eval_point(dpf0, i);
}
// Printing out the results
auto after = prg::count();
std::cout << "Without memoizers: " << "\n";
std::cout << "PRG invocation: " << after-before << "\n";
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "Time of execution: " << ms_double.count() << "ms\n";
// Evaluating the DPF and counting how much it cost with memoizers
auto t3 = high_resolution_clock::now();
before = prg::count();
auto path = dpf::make_basic_path_memoizer(dpf0);
for (int i = 0; i<1024*1024; i++)
{
dpf::eval_point(dpf0, i, path);
}
// Printing out the results
after = prg::count();
std::cout << "With memoizers: " << "\n";
std::cout << "PRG invocation: " << after-before << "\n";
auto t4 = high_resolution_clock::now();
duration<double, std::milli> ms_double2 = t4 - t3;
std::cout << "Time of execution with the memoizers: " << ms_double2.count() << "ms\n";
return 0;
}

View file

@ -0,0 +1,6 @@
#include "dpf.hpp"
int main(int argc, char * argv[])
{
return 0;
}

View file

@ -0,0 +1,14 @@
#include <iostream>
#include "dpf.hpp"
int main()
{
using namespace dpf::literals;
using bits = dpf::bitstring<8>;
bits x = 10101001_bitstring;
std::cout << (x == bits(0b10101001)) << " " << x.to_string() << "\n";
auto [k0, k1] = dpf::make_dpf(x, dpf::bit::one);
auto y0 = dpf::eval_point(k0, x);
auto y1 = dpf::eval_point(k1, x);
std::cout << (static_cast<dpf::bit>(y0) + static_cast<dpf::bit>(y1)) << "\n";
}

View file

@ -0,0 +1,43 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
// Minimal 16-bit input. See test/tests/helpers/custom_input_type.hpp for a
// type that also supports interval evaluation.
struct input_type
{
std::uint16_t i{};
input_type() = default;
explicit constexpr input_type(std::uint16_t v) : i{v} {}
input_type operator&(input_type rhs) const { return input_type{static_cast<std::uint16_t>(i & rhs.i)}; }
input_type operator&(std::uint64_t rhs) const { return input_type{static_cast<std::uint16_t>(i & rhs)}; }
input_type operator>>(std::size_t n) const { return input_type{static_cast<std::uint16_t>(i >> n)}; }
input_type & operator>>=(int) { i = static_cast<std::uint16_t>(i >> 1); return *this; }
explicit operator bool() const { return i != 0; }
};
namespace dpf::utils
{
template <> struct bitlength_of<input_type> : std::integral_constant<std::size_t, 16> {};
template <> struct msb_of<input_type>
{
static constexpr input_type value{std::uint16_t{0x8000}};
};
template <> struct mod_pow_2<input_type>
{
std::size_t operator()(input_type val, std::size_t n) const noexcept
{
if (n == 0) return 0;
const auto mask = n >= 16 ? 0xffffu : (1u << n) - 1u;
return val.i & mask;
}
};
}
int main()
{
auto [k0, k1] = dpf::make_dpf(input_type{7}, dpf::bit::one);
auto y0 = dpf::eval_point(k0, input_type{7});
auto y1 = dpf::eval_point(k1, input_type{7});
std::cout << (static_cast<dpf::bit>(y0) + static_cast<dpf::bit>(y1)) << "\n";
}

View file

@ -0,0 +1,11 @@
#include <iostream>
#include "dpf.hpp"
int main()
{
simde_uint128 x = 42;
auto [k0, k1] = dpf::make_dpf(x, dpf::bit::one);
auto y0 = dpf::eval_point(k0, x);
auto y1 = dpf::eval_point(k1, x);
std::cout << (static_cast<dpf::bit>(y0) + static_cast<dpf::bit>(y1)) << "\n";
}

View file

@ -0,0 +1,25 @@
#include <iostream>
#include <cassert>
#include "dpf.hpp"
int main(int argc, char * argv[])
{
using value_type = int;
std::vector<value_type> values = {12, 34, 56, 78};
auto [dpf0, dpf1] = dpf::make_dpf(value_type{56});
int i=0;
value_type res0{};
auto [buf0, iter0] = dpf::eval_sequence(dpf0, std::begin(values), std::end(values));
for (auto b : iter0) { if (b) res0 ^= values[i]; i++; }
i=0;
value_type res1{};
auto [buf1, iter1] = dpf::eval_sequence(dpf1, std::begin(values), std::end(values));
for (auto b : iter1) { if (b) res1 ^= values[i]; i++; }
std::cout << (res0 ^ res1) << "\n";
std::cout << values[2] << "\n";
}

View file

@ -0,0 +1,28 @@
#include <iostream>
#include <cassert>
#include "dpf.hpp"
int main(int argc, char * argv[])
{
using keyword_type = dpf::keyword<3, dpf::alphabets::lowercase_alpha>;
using value_type = int;
std::vector<keyword_type> keys = {"cat", "dog", "bat", "pig"};
std::vector<value_type> values = {12, 34, 56, 78};
auto [dpf0, dpf1] = dpf::make_dpf(keyword_type{"bat"});
// auto [dpf0, dpf1] = dpf::make_dpf(keyword_type{"rat"}); // key does not exist; result will be 0
int i=0;
value_type res0{};
auto [buf0, iter0] = dpf::eval_sequence(dpf0, std::begin(keys), std::end(keys));
for (auto b : iter0) { if (b) res0 ^= values[i]; i++; }
i=0;
value_type res1{};
auto [buf1, iter1] = dpf::eval_sequence(dpf1, std::begin(keys), std::end(keys));
for (auto b : iter1) { if (b) res1 ^= values[i]; i++; }
std::cout << (res0 ^ res1) << "\n";
std::cout << values[2] << "\n";
}

View file

@ -0,0 +1,32 @@
#include <iostream>
#include <cassert>
#include <map>
#include "dpf.hpp"
int main(int argc, char * argv[])
{
static constexpr char lc[] = "abcdefghijklmnopqrstuvwxyz";
using keyword_type = dpf::keyword<3, lc>;
using value_type = int;
std::map<keyword_type, value_type> database{{"cat", 12},
{"bat", 34},
{"dog", 56},
{"pig", 78}};
keyword_type x = "bat";
std::cout << x << std::endl;
auto [dpf0, dpf1] = dpf::make_dpf(x);
value_type share0{}, share1{};
auto path0 = dpf::make_basic_path_memoizer(dpf0);
auto path1 = dpf::make_basic_path_memoizer(dpf1);
for (auto & [key, value] : database)
{
if (dpf::eval_point(dpf0, key, path0)) {share0 ^= value; std::cout << value << std::endl;}
if (dpf::eval_point(dpf1, key, path1)) share1 ^= value;
}
std::cout << share0 << share1 << "\n";
std::cout << std::string(x) << "(" << x << ")->" << (share0 ^ share1) << "\n";
}

View file

@ -0,0 +1,26 @@
#include <iostream>
#include <cassert>
#include "dpf.hpp"
int main(int argc, char * argv[])
{
dpf::modint<10> a(2048); // 2048 % 2^10 = 0
dpf::modint<10> b(1026); // 1026 % 2^10 = 2
std::cout << a + b << std::endl; // = 2
dpf::modint<10> c(2051); // 2051 % 2^10 = 3
dpf::modint<10> d(1026); // 1026 % 2^10 = 2
std::cout << c - d << std::endl; // = 1
dpf::modint<10> e(2050); // 2050 % 2^10 = 2
dpf::modint<10> f(1026); // 1026 % 2^10 = 2
std::cout << e * f << std::endl; // = 4
dpf::modint<10> g(2052); // 2052 % 2^10 = 4
dpf::modint<10> h(1026); // 1026 % 2^10 = 2
std::cout << g / h << std::endl; // = 2
dpf::modint<10> i(2048); // 2048 % 2^10 = 0
dpf::modint<10> j(1024); // 1024 % 2^10 = 0
std::cout << (i == j) << std::endl; // = 1
}

View file

@ -0,0 +1,13 @@
#include <iostream>
#include "dpf.hpp"
int main()
{
using x8 = dpf::xor_wrapper<std::uint8_t>;
x8 x{0x3c};
auto [k0, k1] = dpf::make_dpf(x, dpf::bit::one);
auto y0 = dpf::eval_point(k0, x);
auto y1 = dpf::eval_point(k1, x);
std::cout << (static_cast<dpf::bit>(y0) + static_cast<dpf::bit>(y1)) << "\n";
std::cout << ((++x8{1}) == x8{0}) << "\n";
}

View file

@ -0,0 +1,23 @@
#include <iostream>
#include "dpf.hpp"
int main(int argc, char * argv[])
{
using input_type = uint16_t;
using output_type = dpf::bit;
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128, input_type, output_type>;
auto memo0 = dpf::make_basic_full_memoizer<dpf_type>();
auto memo1 = dpf::make_basic_full_memoizer<dpf_type>();
input_type x = 42;
output_type y = dpf::bit::one;
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
auto advice0 = dpf::advice_bits_of(memo0);
auto advice1 = dpf::advice_bits_of(memo1);
auto it0 = std::begin(advice0), it1 = std::begin(advice1);
for (std::size_t i = 0; i < std::size_t(1)<<dpf_type::depth; ++i, ++it0, ++it1)
{
std::cout << "Advice bit " << i << " of dpf0: " << *it0 << "\n";
}
}

View file

@ -0,0 +1,6 @@
#include "dpf.hpp"
int main(int argc, char * argv[])
{
return 0;
}

View file

@ -0,0 +1,6 @@
#include "dpf.hpp"
int main(int argc, char * argv[])
{
return 0;
}

View file

@ -0,0 +1,6 @@
#include "dpf.hpp"
int main(int argc, char * argv[])
{
return 0;
}

View file

@ -0,0 +1,6 @@
#include "dpf.hpp"
int main(int argc, char * argv[])
{
return 0;
}

View file

@ -0,0 +1,6 @@
#include "dpf.hpp"
int main(int argc, char * argv[])
{
return 0;
}

View file

@ -0,0 +1,10 @@
#include <iostream>
#include "dpf.hpp"
int main()
{
auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, dpf::bit::one);
auto y0 = dpf::eval_point(k0, std::uint8_t{3});
auto y1 = dpf::eval_point(k1, std::uint8_t{3});
std::cout << (static_cast<dpf::bit>(y0) + static_cast<dpf::bit>(y1)) << "\n";
}

View file

@ -0,0 +1,12 @@
#include <iostream>
#include "dpf.hpp"
int main()
{
using bits = dpf::bitstring<8>;
auto programmed = bits(0b10101001);
auto [k0, k1] = dpf::make_dpf(std::uint8_t{2}, programmed);
auto y0 = dpf::eval_point(k0, std::uint8_t{2});
auto y1 = dpf::eval_point(k1, std::uint8_t{2});
std::cout << (static_cast<bits>(y0) + static_cast<bits>(y1)) << "\n";
}

View file

@ -0,0 +1,26 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
struct point
{
std::uint32_t v{};
point operator+(point rhs) const { return point{v + rhs.v}; }
point operator-(point rhs) const { return point{v - rhs.v}; }
bool operator==(point rhs) const { return v == rhs.v; }
};
namespace dpf::leaf_arithmetic
{
template <> struct add_t<point, simde__m128i> final : detail::add4x32_t {};
template <> struct subtract_t<point, simde__m128i> final : detail::sub4x32_t {};
template <> struct multiply_t<point, simde__m128i> final : detail::mul4x32_t {};
}
int main()
{
auto [k0, k1] = dpf::make_dpf(std::uint8_t{9}, point{5});
auto y0 = dpf::eval_point(k0, std::uint8_t{9});
auto y1 = dpf::eval_point(k1, std::uint8_t{9});
std::cout << (static_cast<point>(y0) + static_cast<point>(y1)).v << "\n";
}

View file

@ -0,0 +1,11 @@
#include <iostream>
#include "dpf.hpp"
int main()
{
auto [k0, k1] = dpf::make_dpf(std::uint8_t{1}, simde_uint128{7});
auto y0 = dpf::eval_point(k0, std::uint8_t{1});
auto y1 = dpf::eval_point(k1, std::uint8_t{1});
auto sum = static_cast<simde_uint128>(y0) + static_cast<simde_uint128>(y1);
std::cout << (sum == simde_uint128{7}) << "\n";
}

View file

@ -0,0 +1,11 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
int main()
{
auto [k0, k1] = dpf::make_dpf(std::uint16_t{42}, std::uint32_t{7});
auto y0 = dpf::eval_point(k0, std::uint16_t{42});
auto y1 = dpf::eval_point(k1, std::uint16_t{42});
std::cout << (static_cast<std::uint32_t>(y0) + static_cast<std::uint32_t>(y1)) << "\n";
}

View file

@ -0,0 +1,18 @@
#include <iostream>
#include "dpf.hpp"
int main(int argc, char * argv[])
{
using input_type = uint8_t;
using output_type = dpf::wildcard_value<uint32_t>;
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128, input_type, output_type>;
input_type x = 12;
output_type y;
auto [dpf0, dpf1] = dpf::make_dpf(x, y);
std::array<input_type, 5> points{12, 34, 56, 78, 90};
auto recipe0 = dpf::make_sequence_recipe(dpf0, std::begin(points), std::end(points));
auto recipe1 = dpf::make_sequence_recipe(dpf1, std::begin(points), std::end(points));
}

View file

@ -0,0 +1,11 @@
#include <iostream>
#include "dpf.hpp"
int main()
{
using x32 = dpf::xor_wrapper<std::uint32_t>;
auto [k0, k1] = dpf::make_dpf(std::uint8_t{4}, x32{0x11});
auto y0 = dpf::eval_point(k0, std::uint8_t{4});
auto y1 = dpf::eval_point(k1, std::uint8_t{4});
std::cout << (static_cast<x32>(y0) + static_cast<x32>(y1)) << "\n";
}

119
include/dpf.hpp Normal file
View file

@ -0,0 +1,119 @@
/// @file dpf.hpp
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @brief includes all headers needed for basic libdpf++ functionality
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_HPP__
#define LIBDPF_INCLUDE_DPF_HPP__
#include "dpf/advice_bit_iterable.hpp"
#include "dpf/aligned_allocator.hpp"
#ifdef LIBDPF_HAS_ASIO
#include "dpf/asio.hpp"
#endif // LIBDPF_HAS_ASIO
#include "dpf/bit_array.hpp"
#include "dpf/bit.hpp"
#include "dpf/twobit.hpp"
#include "dpf/nyble.hpp"
#include "dpf/bitstring.hpp"
#include "dpf/dpf_key.hpp"
#include "dpf/doerner_shelat.hpp"
#include "dpf/geneval.hpp"
#include "dpf/incremental.hpp"
#include "dpf/eval_common.hpp"
#include "dpf/eval_interval.hpp"
#include "dpf/eval_inner_product.hpp"
#include "dpf/eval_full.hpp"
#include "dpf/eval_point.hpp"
#include "dpf/eval_sequence.hpp"
#include "dpf/eval_unified.hpp"
#include "dpf/interval_memoizer.hpp"
#ifdef LIBDPF_HAS_NLOHMANN_JSON
#ifndef NLOHMANN_JSON_VERSION_MAJOR
// was told you use nlohmann::json, but it's not available!
#else
#include "dpf/json.hpp"
#endif // NLOHMANN_JSON_VERSION_MAJOR
#endif // LIBDPF_HAS_NLOHMANN_JSON
#include "dpf/keyword.hpp"
#include "dpf/keyword2.hpp"
#include "dpf/leaf_arithmetic.hpp"
#include "dpf/leaf_node.hpp"
#include "dpf/literals.hpp"
#include "dpf/modint.hpp"
#include "dpf/output_buffer.hpp"
#include "dpf/parallel_bit_iterable_helpers.hpp"
#include "dpf/parallel_bit_iterable.hpp"
#include "dpf/path_memoizer.hpp"
#include "dpf/prg.hpp"
#include "dpf/rotation_iterable.hpp"
#include "dpf/random.hpp"
#include "dpf/buffered_prg.hpp"
#include "dpf/beaver.hpp"
#include "dpf/secret_share.hpp"
#include "dpf/rotation_iterable.hpp"
#include "dpf/sequence_memoizer.hpp"
#include "dpf/sequence_recipe.hpp"
#include "dpf/sequence_utils.hpp"
#include "dpf/setbit_index_iterable.hpp"
#include "dpf/subinterval_iterable.hpp"
#include "dpf/subsequence_iterable.hpp"
#include "dpf/twiddle.hpp"
#include "dpf/utils.hpp"
#include "dpf/wildcard.hpp"
#include "dpf/xor_wrapper.hpp"
#include "dpf/zip_iterable.hpp"
#include "dpf/uint256_t.hpp"
#endif // LIBDPF_INCLUDE_DPF_HPP__

View file

@ -0,0 +1,426 @@
/// @file dpf/advice_bit_iterable.hpp
/// @brief defines `dpf::advice_bit_iterable` and associated helpers
/// @details A `dpf::advice_bit_iterable` is a convenience class that wraps an
/// existing iterable type to provide a new iterable over advice bits
/// (i.e., over the least-significant bit of each element). The `begin`
/// and `end` member functions of the `dpf::advice_bit_iterable` class
/// each return `LegacyForwardIterator`s compatible with standard
/// library algorithms and range-based loops.
///
/// In addition to `dpf::advice_bit_iterable`, this file defines the
/// following helper functions:
/// - `advice_bits_of`: wraps an iterable type to simplify notation
/// for range-based loops. For example, it lets you write
/// \code{cpp}
/// for (auto b : advice_bits_of(my_iterable)) foo(b);
/// \endcode
/// instead of
/// \code{cpp}
/// advice_bit_iterable advice_bits{my_iterable};
/// for (auto b : advice_bits) foo(b);
/// \endcode
/// - `for_each_advice_bit`: iterate through and apply a given
/// function to each advice bit
/// - `bit_array_from_advice_bits`: constructs a
/// `dpf::dynamic_bit_array` that holds the advice bits of the
/// underlying iterable.
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @author Christopher Jiang <christopher.jiang@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_ADVICE_BIT_ITERABLE_HPP__
#define LIBDPF_INCLUDE_DPF_ADVICE_BIT_ITERABLE_HPP__
#include "hedley/hedley.h"
#include <cstddef>
#include <cstring>
#include <type_traits>
#include <iterator>
#include <memory>
#include <algorithm>
#include <array>
#include "simde/simde/x86/avx2.h"
#include "portable-snippets/exact-int/exact-int.h"
#include "dpf/utils.hpp"
#include "dpf/bit_array.hpp"
namespace dpf
{
namespace detail
{
template <typename Iterator>
struct extract_bit_simde_node
{
bool operator()(Iterator it) const
{
auto buf = reinterpret_cast<const char *>(&*it);
return buf[0] & 1;
}
};
template <typename NodeT, typename Iterator>
struct extract_bit;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
template <typename Iterator>
struct extract_bit<simde__m128i, Iterator>
: public extract_bit_simde_node<Iterator> { };
template <typename Iterator>
struct extract_bit<simde__m256i, Iterator>
: public extract_bit_simde_node<Iterator> { };
HEDLEY_PRAGMA(GCC diagnostic pop)
} // namespace detail
template <typename WrappedIteratorType>
class advice_bit_iterable_const_iterator;
template <typename Iterable>
class advice_bit_iterable
{
public:
using wrapped_iterator_type = typename Iterable::iterator_type;
using const_iterator
= advice_bit_iterable_const_iterator<wrapped_iterator_type>;
explicit advice_bit_iterable(const Iterable & iterable)
: begin_{std::begin(iterable)}, end_{std::end(iterable)}
{ }
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
const_iterator begin() const noexcept
{
return const_iterator(begin_);
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
const_iterator cbegin() const noexcept
{
return begin();
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
const_iterator end() const noexcept
{
return const_iterator(end_);
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
const_iterator cend() const noexcept
{
return end();
}
private:
const wrapped_iterator_type begin_, end_;
}; // class dpf::advice_bit_iterable
template <typename WrappedIteratorType>
class advice_bit_iterable_const_iterator
{
public:
using iterator_traits = std::iterator_traits<WrappedIteratorType>;
using wrapped_type = WrappedIteratorType;
using value_type = bool;
using reference = value_type;
using const_reference = reference;
using pointer = std::add_pointer_t<reference>;
using iterator_category = typename iterator_traits::iterator_category;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using node_type = typename iterator_traits::value_type;
HEDLEY_ALWAYS_INLINE
constexpr
explicit advice_bit_iterable_const_iterator(const wrapped_type & it) noexcept
: it_{it}
{ }
HEDLEY_ALWAYS_INLINE
constexpr
advice_bit_iterable_const_iterator(advice_bit_iterable_const_iterator &&)
= default;
HEDLEY_ALWAYS_INLINE
constexpr
advice_bit_iterable_const_iterator(
const advice_bit_iterable_const_iterator &) = default;
advice_bit_iterable_const_iterator & operator=(
const advice_bit_iterable_const_iterator &) = default;
advice_bit_iterable_const_iterator & operator=(
advice_bit_iterable_const_iterator &&) = default;
~advice_bit_iterable_const_iterator() = default;
HEDLEY_PURE
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr reference operator*() const noexcept
{
return bit(it_);
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr advice_bit_iterable_const_iterator & operator++() noexcept
{
++it_;
return *this;
}
HEDLEY_NO_THROW
advice_bit_iterable_const_iterator operator++(int) noexcept
{
auto tmp = *this;
advice_bit_iterable_const_iterator::operator++();
return tmp;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
advice_bit_iterable_const_iterator & operator--() noexcept
{
--it_;
return *this;
}
HEDLEY_NO_THROW
advice_bit_iterable_const_iterator operator--(int) noexcept
{
auto tmp = *this;
advice_bit_iterable_const_iterator::operator--();
return tmp;
}
advice_bit_iterable_const_iterator & operator+=(std::size_t n) noexcept
{
it_ += n;
return *this;
}
advice_bit_iterable_const_iterator operator+(std::size_t n) const noexcept
{
return advice_bit_iterable_const_iterator(it_ + n);
}
advice_bit_iterable_const_iterator & operator-=(std::size_t n) noexcept
{
it_ -= n;
return *this;
}
advice_bit_iterable_const_iterator operator-(std::size_t n) const noexcept
{
return advice_bit_iterable_const_iterator(it_ - n);
}
difference_type
operator-(advice_bit_iterable_const_iterator rhs) const noexcept
{
return it_ - rhs.it_;
}
reference operator[](std::size_t i) const noexcept
{
return bit(it_ + i);
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bool
operator==(const advice_bit_iterable_const_iterator & rhs) const noexcept
{
return it_ == rhs.it_;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bool
operator<(const advice_bit_iterable_const_iterator & rhs) const noexcept
{
return it_ < rhs.it_;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bool
operator!=(const advice_bit_iterable_const_iterator & rhs) const noexcept
{
return !(*this == rhs);
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bool
operator>(const advice_bit_iterable_const_iterator & rhs) const noexcept
{
return rhs < *this;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bool
operator<=(const advice_bit_iterable_const_iterator & rhs) const noexcept
{
return !(rhs < *this);
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr bool
operator>=(const advice_bit_iterable_const_iterator & rhs) const noexcept
{
return !(*this < rhs);
}
private:
wrapped_type it_;
static constexpr auto bit = detail::extract_bit<node_type, wrapped_type>{};
}; // class dpf::advice_bit_iterable_const_iterator
template <typename Iterable>
dpf::advice_bit_iterable<Iterable> advice_bits_of(const Iterable & iterable)
{
return advice_bit_iterable<Iterable>{iterable};
}
template <typename Iterable,
typename UnaryFunction>
void for_each_advice_bit(const Iterable & iterable, UnaryFunction f)
{
for (auto i : advice_bits_of(iterable)) f(i);
}
namespace detail
{
template <typename Iterator>
auto bit_array_from_advice_bits_small(Iterator first, Iterator last,
std::size_t bits)
{
auto ret = dynamic_bit_array(bits);
auto curbit = ret.begin();
for (; first != last; ++first)
{
(*curbit++).assign(*first);
}
return ret;
}
template <typename Iterator>
auto bit_array_from_advice_bits_simde(Iterator first, Iterator last,
std::size_t bits)
{
using simde_type = simde__m256i;
using simde_ptr = simde_type *;
static_assert(CHAR_BIT == 8, "CHAR_BIT not equal to 8");
auto ret = dynamic_bit_array(bits);
std::size_t bits_per_byte = CHAR_BIT,
bytes = (bits-1)/bits_per_byte + 1,
bits_per_word = ret.bits_per_word,
bits_per_simde = dpf::utils::bitlength_of_v<simde_type>,
bytes_per_simde = sizeof(simde_type),
words_per_simde = bits_per_simde / bits_per_word;
std::size_t curbits = 0, pos = 0;
std::array<char, 32> in = {0};
std::array<psnip_uint32_t, 8> out;
while (curbits < bits)
{
simde_type simde = {0, 0, 0, 0};
std::size_t i = 0;
for (; i < bits_per_byte && curbits < bits; ++i)
{
for (std::size_t j = 0; j < 32 && curbits < bits; ++j, ++curbits)
{
in[j] = *first++;
}
auto tmp = reinterpret_cast<simde_ptr>(std::data(in));
simde = simde_mm256_or_si256(
simde_mm256_slli_epi64(simde, 1),
simde_mm256_loadu_si256(tmp));
}
// algorithm expects "first bit" to be MSB in each 8-bit block at next step
for (std::size_t j = i; j < bits_per_byte; ++j)
{
simde = simde_mm256_slli_epi64(simde, 1);
}
for (std::size_t j = 0; j < i; ++j)
{
out[j] = simde_mm256_movemask_epi8(simde);
simde = simde_mm256_slli_epi64(simde, 1);
}
auto dst = reinterpret_cast<char *>(
std::addressof(ret.data(pos++ * words_per_simde)));
auto src = reinterpret_cast<char *>(std::data(out));
std::memcpy(dst, src, std::min(bytes_per_simde, bytes));
bytes -= bytes_per_simde;
}
return ret;
}
} // namespace detail
template <std::size_t NbitsCrossover = 1 << 4,
typename Iterator>
auto
bit_array_from_advice_bits(const advice_bit_iterable<Iterator> & advice_bits)
{
auto first = std::begin(advice_bits), last = std::end(advice_bits);
std::size_t bits = std::distance(first, last);
if (bits < NbitsCrossover)
{
return detail::bit_array_from_advice_bits_small(first, last, bits);
}
else
{
return detail::bit_array_from_advice_bits_simde(first, last, bits);
}
}
} // namespace dpf
namespace std
{
template <typename Iterator>
struct iterator_traits<dpf::advice_bit_iterable_const_iterator<Iterator>>
{
private:
using type = dpf::advice_bit_iterable_const_iterator<Iterator>;
public:
using iterator_category = typename type::iterator_category;
using difference_type = typename type::difference_type;
using value_type = typename type::value_type;
using reference = typename type::reference;
using const_reference = typename type::const_reference;
using pointer = typename type::pointer;
};
} // namespace std
#endif // LIBDPF_INCLUDE_DPF_ADVICE_BIT_ITERABLE_HPP__

View file

@ -0,0 +1,227 @@
/// @file dpf/aligned_allocator.hpp
/// @brief defines an allocator that aligns memory allocations to a specified alignment
/// @details The `dpf::aligned_allocator` class template is used to allocate
/// uninitialized memory with a specified alignment for all `libdpf++`
/// buffers and memoizers, if no user-specified allocator is
/// provided. It is stateless, so all instances of the allocator are
/// interchangeable. The alignment is specified by the Alignment
/// parameter, which must be a power of two (default:
/// `dpf::utils::max_align_v`).
///
/// The allocator supports the `dpf::aligned_allocator::allocate()` function for allocating
/// aligned, yet uninitialized memory and the `dpf::aligned_allocator::deallocate()` function
/// for freeing the same. It also includes a convenient
/// `dpf::aligned_allocator::allocate_unique_ptr()` function that returns a `std::unique_ptr`
/// to the output of a call to `dpf::aligned_allocator::allocate()`.
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_ALIGNED_ALLOCATOR_HPP__
#define LIBDPF_INCLUDE_DPF_ALIGNED_ALLOCATOR_HPP__
#include <cstddef>
#include <cstdlib>
#include <type_traits>
#include <memory>
#include <limits>
#include <new>
#include "hedley/hedley.h"
#include "simde/simde/x86/avx2.h"
namespace dpf
{
/// @brief an allocator that allocates aligned memory
/// @details The `dpf::aligned_allocator` class template is the default memory
/// allocator used by all `libdpf++` buffers and memoizers, if no
/// user-specified allocator is provided. It allocates uninitialized
/// storage whose alignment is specified by `Alignment` and whose
/// size is an integral multiple of `sizeof(T)`. The allocator is
/// stateless; that is, all instances of the given allocator are
/// interchangeable and can deallocate memory allocated by any other
/// instance of the same allocator type.
/// @tparam T the type to allocate
/// @tparam Alignment specifies the alignment (default: `dpf::utils::max_align`).'
/// The program is ill-formed if `Alignment` is not a power of 2.
template <typename T,
std::size_t Alignment = alignof(T)>
class aligned_allocator
{
private:
/// @brief a `deleter` functor for use by `std::unique_ptr<T[]>` to free
/// memory allocated when the `std::unique_ptr<T[]>` was
/// constructed
template <typename Pointer>
struct deleter
{
constexpr void operator()(Pointer p) const noexcept { free(p); }
};
public:
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using unique_ptr = std::unique_ptr<value_type[], deleter<pointer>>;
using const_pointer = const value_type *;
using reference = value_type &;
using const_reference = const value_type &;
static constexpr size_type alignment = Alignment;
/// @brief class whose member `other` is a typedef of
/// `dpf::aligned_allocator` for some type `U` with alignment `A`.
/// @tparam U the type to rebind to
/// @tparam A the alignment of the rebound allocator
template <typename U, size_type A = alignment> struct rebind
{
using other = aligned_allocator<U, A>;
};
/// @name Constructors
/// @brief Constructs the default allocator. Since the default allocator
/// is stateless, the constructors have no visible effect.
/// @{
/// @brief Default constructor
/// @details Constructs an instance of `dpf::aligned_allocator`.
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr aligned_allocator() noexcept = default;
/// @brief Copy constructor
/// @details Constructs an instance of `dpf::aligned_allocator` from another
/// using copy semantics.
/// @param other another `dpf::aligned_allocator` to construct with
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr aligned_allocator(const aligned_allocator & other) noexcept
= default;
/// @brief Move constructor
/// @details Constructs an instance of `dpf::aligned_allocator` from another
/// using move semantics.
/// @param other another `dpf::aligned_allocator` to construct with
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr aligned_allocator(aligned_allocator && other) noexcept = default;
/// @}
/// @{
aligned_allocator & operator=(const aligned_allocator &) noexcept = default;
aligned_allocator & operator=(aligned_allocator &&) noexcept = default;
/// @}
/// @brief D'tor
/// @details Destroys an instance of `dpf::aligned_allocator`.
~aligned_allocator() = default;
/// @brief returns the largest supported allocation size
/// @details Returns the maximum theoretically possible value of `num`,
/// for which the call `allocate(num)` could succeed.
/// @note This function returns the maximum number of elements that can
/// be allocated, not the maximum allocation size in bytes
/// @return The maximum supported allocation size.
constexpr size_type max_size() const noexcept
{
return std::numeric_limits<size_type>::max() / sizeof(value_type);
}
/// @brief allocates aligned, yet uninitialized storage
/// @details Allocates `num * sizeof(T)` bytes of uninitialized
/// storage by invoking
/// `std::aligned_alloc(alignment, num * sizeof(T))`.
/// @param num the number of instances of `T` to allocate storage for
/// @return Pointer to the first element of an array of `num` instaces
/// of type `T` whose elements have not been constructed yet.
/// @throws std::bad_array_new_length if `max_size() < num`
/// @throws std::bad_alloc if allocation fails.
HEDLEY_WARN_UNUSED_RESULT
HEDLEY_MALLOC
HEDLEY_RETURNS_NON_NULL
constexpr
pointer allocate(size_type num, const void * /*hint*/ = nullptr) const
{
if (max_size() < num)
{
throw std::bad_array_new_length();
}
// C11 `aligned_alloc` requires the size to be a multiple of the
// alignment. Round up so odd element counts (or odd sizeof(T))
// do not pass a non-conforming size and corrupt the heap.
const size_type bytes = num * sizeof(T);
const size_type aligned_bytes =
(bytes + (alignment - 1)) & ~(alignment - 1);
void * ptr = std::aligned_alloc(alignment, aligned_bytes);
if (ptr == nullptr)
{
throw std::bad_alloc();
}
return assume_aligned(static_cast<pointer>(ptr));
}
/// @brief allocates and constructs a `std::unqiue_ptr<T[]>` to aligned, yet
/// uninitialized storage
/// @details Allocates `num * sizeof(T)` bytes of uninitialized
/// storage by invoking `allocate(size_type, const void *)` and
/// returns a `std::unique_ptr<T[]>` that owns it.
/// @param num the number of instances of `T` to allocate storage for
/// @return An `std::unique_ptr<T[]>` owning the pointer to the first
/// element of an array of `num` instaces of type `T` whose elements
/// have not been constructed yet.
/// @throws std::bad_array_new_length if `max_size() < num`
/// @throws std::bad_alloc if allocation fails.
HEDLEY_ALWAYS_INLINE
constexpr auto allocate_unique_ptr(size_type num) const
{
return unique_ptr{allocate(num)};
}
/// @brief deallocates storage
/// @details Deallocates the storage referenced by the pointer `p`,
/// which must be a pointer obtained by an earlier call to
/// `allocate()`.
/// @param p pointer obtained from allocate()
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr void deallocate(pointer p, size_type /*num*/ = 0) const noexcept
{
free(p);
}
/// @brief informs the compiler that a pointer is aligned
/// @details Informs the implementation that the object ptr points to is
/// aligned to at least `alignment`. The implementation may use
/// this information to generate more efficient code, but it might
/// only make this assumption if the object is accessed via the
/// return value of `assume_aligned`.
///
/// The behavior is undefined if `ptr` does not point to an object
/// of type `T` (ignoring cv-qualification at every level), or if the
/// object's alignment is not at least `Alignment`.
/// @note It is up to the program to ensure that the alignment assumption
/// actually holds. A call to `assume_aligned` does not cause the
/// compiler to verify or enforce this.
/// @param ptr the pointer
/// @return `ptr`
HEDLEY_WARN_UNUSED_RESULT
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_NON_NULL(1)
constexpr static auto assume_aligned(pointer ptr) noexcept
{
return static_cast<pointer>(
__builtin_assume_aligned(ptr, alignment));
}
};
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_ALIGNED_ALLOCATOR_HPP__

1167
include/dpf/asio.hpp Normal file

File diff suppressed because it is too large Load diff

2441
include/dpf/beaver.hpp Normal file

File diff suppressed because it is too large Load diff

266
include/dpf/bit.hpp Normal file
View file

@ -0,0 +1,266 @@
/// @file dpf/bit.hpp
/// @brief defines `dpf::bit` and associated helpers
/// @details A `dpf::bit` is a binary type whose representation can be packed
/// into one bit. It is implemented as an `enum` with two values:
/// `zero` and `one`. This type is intended for us as an [output type](@ref output_types)
/// for a DPF, in which case leaf nodes will be packed in much
/// the ways as in an `std::bitset` or `std::vector<bool>`.
///
/// In addition to `dpf::bit`, this file defines three overloaded
/// variants of a `dpf::to_bit` function that respectively convert
/// a `bool, a `char`, or (the least significant bit of) an `int` to
/// a `dpf::bit`. Likewise, it defines `dpf::to_string` to convert
/// a `dpf::bit` into an `std::string`. Finally, it overloads stream
/// input and output operators (`<<` and `>>`) for `dpf::bit`.
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_BIT_HPP__
#define LIBDPF_INCLUDE_DPF_BIT_HPP__
#include <cstddef>
#include <type_traits>
#include <limits>
#include <stdexcept>
#include <string>
#include <memory>
#include <ostream>
#include <istream>
#include "hedley/hedley.h"
#include "dpf/utils.hpp"
/// @brief the dpf namespace
namespace dpf
{
/// @brief binary type whose representation can be packed into one bit
enum bit : bool
{
zero = false, ///< `0`, `false`, "unset", "off"
one = true ///< `1`, `true`, "set", "on"
};
/// @brief converts a value to a `dpf::bit`
/// @{
/// @brief converts (the lsb of) an `int` to a `dpf::bit`
/// @details Convert an `int` to a `dpf::bit`. The resulting `dpf::bit` is
/// equal to `dpf::bit::one` if the *least-significant bit* of
/// `value` is `1` and `dpf::bit::zero` otherwise.
/// @param value the `int` to convert
/// @returns `static_cast<dpf::bit>(value & 1)`
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
static constexpr dpf::bit to_bit(int value) noexcept
{
return static_cast<dpf::bit>(value & 1);
}
/// @brief converts the least-significant bit of an integer literal to a `dpf::bit`
/// @details This overload exists so `operator""_bit` does not select the
/// character converter, which is an exact match for `unsigned long long`.
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
static constexpr dpf::bit to_bit(unsigned long long value) noexcept
{
return static_cast<dpf::bit>(value & 1ull);
}
/// @brief converts a `bool` to a `dpf::bit`
/// @details Convert a `bool` to a `dpf::bit`. The resulting `dpf::bit` is
/// equal to `dpf::bit::one` if `value==true` and `dpf::bit::zero`
/// otherwise.
/// @param value the `bool` to convert
/// @returns `static_cast<dpf::bit>(value)`
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
static constexpr dpf::bit to_bit(bool value) noexcept
{
return static_cast<dpf::bit>(value);
}
/// @brief converts a character to a `dpf::bit`
/// @details Convert a character to a `dpf::bit`. The resulting `dpf::bit` is
/// equal to `dpf::bit::one` if `value==one` and `dpf::bit::zero`
/// otherwise.
/// @param value the character to convert
/// @param zero character used to represent `0` (default: ``CharT('0')``)
/// @param one character used to represent `1` (default: ``CharT('1')``)
/// @returns `static_cast<dpf::bit>(0)` if `value==0` or
/// `static_cast<dpf::bit>(1)` if `value==1`
/// @throws std::domain_error if `value != zero && value != one`
template <typename CharT,
typename Traits = std::char_traits<CharT>>
HEDLEY_ALWAYS_INLINE
static constexpr dpf::bit to_bit(
CharT value,
CharT zero = CharT('0'),
CharT one = CharT('1'))
{
if (!Traits::eq(value, zero) && !Traits::eq(value, one))
{
throw std::domain_error("Unrecognized character");
}
return Traits::eq(value, zero) ? dpf::bit::zero : dpf::bit::one;
}
/// @}
/// @brief converts a `dpf::bit` to a `std::basic_string`
/// @details Converts the contents of a `dpf::bit` to a `std::string` for
/// human-friendly printing. Uses `zero` to represent the value
/// `0` and `one` to the value `1`.
/// @param value the `dpf::bit` to convert
/// @param zero character to use to represent `false`/`0` (default: ``CharT('0')``)
/// @param one character to use to represent `true`/`1` (default: ``CharT('1')``)
/// @return `(value == 0) ? zero : one`
template <typename CharT = char,
typename Traits = std::char_traits<CharT>,
typename Allocator = std::allocator<CharT>>
static std::basic_string<CharT, Traits, Allocator> to_string(
dpf::bit value,
CharT zero = CharT('0'),
CharT one = CharT('1'))
{
auto ch = (value == dpf::bit::zero) ? zero : one;
return std::basic_string<CharT>(1, ch, Allocator{});
}
/// @brief performs stream input and output on `dpf::bit`s
/// @{
/// @brief performs stream output on a `dpf::bit`
/// @details Writes a `dpf::bit` to the character stream `os` as if by first
/// converting it to a `std::basic_string<CharT, Traits>` using
/// `dpf::to_string()`, and then writing it into `os` using `operator<<`
/// (which is a `FormattedOutputFunction` for strings). The
/// characters to use for zero and one are obtained from the
/// currently-imbued locale by calling `os.widen()` with `0` and `1`
/// as the arguments.
/// @param os a character output stream
/// @param value the `dpf::bit` to insert into the output stream
/// @return `os`
template <typename CharT,
typename Traits>
std::basic_ostream<CharT, Traits> &
operator<<(std::basic_ostream<CharT, Traits> & os, const dpf::bit & value)
{
return os << to_string<CharT, Traits>(value, os.widen('0'),
os.widen('1'));
}
/// @brief performs stream input on a `dpf::bit`
/// @details Extracts one character from `is` and attempts to convert it to
/// a `dpf::bit` using `dpf::to_bit()`. If successful, the result is
/// stored in `value`. The characters to use for zero and one are
/// obtained from the currently-imbued locale by calling `is.widen()`
/// with `0` and `1` as the arguments.
/// @param is a character input stream
/// @param value the `dpf::bit` to extract from the input stream
/// @return `is`
template <typename CharT,
typename Traits>
std::basic_istream<CharT, Traits> &
operator>>(std::basic_istream<CharT, Traits> & is, dpf::bit & value)
{
try
{
value = to_bit<CharT>(is.get(), is.widen('0'), is.widen('1'));
}
catch(const std::exception & e)
{
is.setstate(std::ios::failbit);
}
return is;
}
/// @}
inline constexpr dpf::bit operator+(dpf::bit lhs, dpf::bit rhs) noexcept
{
return static_cast<dpf::bit>(static_cast<bool>(lhs) ^ static_cast<bool>(rhs));
}
/// @brief GF(2) subtraction. Identical to `operator+`.
inline constexpr dpf::bit operator-(dpf::bit lhs, dpf::bit rhs) noexcept
{
return lhs + rhs;
}
namespace utils
{
/// @brief specializes `dpf::utils::bitlength_of` for `dpf::bit`
template <>
struct bitlength_of<dpf::bit>
: public std::integral_constant<std::size_t, 1> { };
template <typename NodeT>
struct bitlength_of_output<dpf::bit, NodeT>
: public std::integral_constant<std::size_t, 1> { };
template <>
struct is_packed_subbyte<dpf::bit> : std::true_type {};
template <>
struct packed_lane_bits<dpf::bit>
: public std::integral_constant<std::size_t, 1> {};
template <>
struct make_from_integral_value<dpf::bit>
{
constexpr dpf::bit operator()(bool val) const noexcept
{
return val ? dpf::bit::one : dpf::bit::zero;
}
};
} // namespace utils
namespace literals
{
namespace bit
{
constexpr static auto operator "" _bit(unsigned long long int x) { return dpf::to_bit(x); }
} // namespace bit
} // namespace literals
} // namespace dpf
namespace std
{
/// @{
/// @brief specializes `std::numeric_limits` for `dpf::bit`
template<> class numeric_limits<dpf::bit>
: public numeric_limits<bool> { };
/// @brief specializes `std::numeric_limits` for `dpf::bit const`
template<> class numeric_limits<dpf::bit const>
: public numeric_limits<dpf::bit> {};
/// @brief specializes `std::numeric_limits` for `dpf::bit volatile`
template<> class numeric_limits<dpf::bit volatile>
: public numeric_limits<dpf::bit> {};
/// @brief specializes `std::numeric_limits` for `dpf::bit const volatile`
template<> class numeric_limits<dpf::bit const volatile>
: public numeric_limits<dpf::bit> {};
/// @}
} // namespace std
#endif // LIBDPF_INCLUDE_DPF_BIT_HPP__

1694
include/dpf/bit_array.hpp Normal file

File diff suppressed because it is too large Load diff

1179
include/dpf/bitstring.hpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,349 @@
/// @file dpf/buffered_prg.hpp
/// @brief Buffered, seekable lanes over a libdpf PRG.
/// @details The default PRG is `dpf::prg::aes128`. Any PRG with
/// `block_type`, `eval(seed, pos)`, and `eval(seed, out, count, pos)`
/// can be substituted. A master block is the recorded seed. Role `r`
/// owns two streams: values are `PRG::eval(master, r)` and share
/// masks are `PRG::eval(tweaked_master, r)`. Element `index` is a
/// contiguous run of blocks on that stream, so a forward scan is one
/// multi-block `eval`.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_BUFFERED_PRG_HPP__
#define LIBDPF_INCLUDE_DPF_BUFFERED_PRG_HPP__
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <map>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "hedley/hedley.h"
#include "dpf/aligned_allocator.hpp"
#include "dpf/prg.hpp"
#include "dpf/random.hpp"
namespace dpf
{
namespace randomness
{
namespace detail
{
template <typename PRG>
typename PRG::block_type mask_master(typename PRG::block_type master) noexcept
{
unsigned char raw[sizeof(master)];
std::memcpy(raw, &master, sizeof(master));
raw[sizeof(master) - 1] ^= 0x01u;
typename PRG::block_type out{};
std::memcpy(&out, raw, sizeof(out));
return out;
}
template <typename PRG, typename T>
struct lane_codec
{
static_assert(std::is_trivially_copyable_v<T>,
"prg lanes require a trivially copyable value type");
using block_type = typename PRG::block_type;
static T at(block_type seed, std::uint64_t index)
{
T out{};
fill(seed, index, &out, 1u);
return out;
}
/// Element `index` is the packed byte range `[index * sizeof(T), ...)`.
static void fill(block_type seed, std::uint64_t index, T * out, std::size_t count)
{
if (count == 0)
return;
constexpr std::uint64_t block_bytes = sizeof(block_type);
std::uint64_t byte_off = index * static_cast<std::uint64_t>(sizeof(T));
std::uint64_t nbytes = static_cast<std::uint64_t>(count) * sizeof(T);
std::uint64_t start = byte_off / block_bytes;
std::uint64_t end = byte_off + nbytes;
std::uint64_t nblocks = (end + block_bytes - 1u) / block_bytes - start;
if (start > static_cast<std::uint64_t>(UINT32_MAX)
|| nblocks > static_cast<std::uint64_t>(UINT32_MAX)
|| start > static_cast<std::uint64_t>(UINT32_MAX) - nblocks)
{
throw std::invalid_argument("prg lane index is out of range");
}
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::aligned_allocator<block_type> alloc;
auto blocks = alloc.allocate_unique_ptr(static_cast<std::size_t>(nblocks));
HEDLEY_PRAGMA(GCC diagnostic pop)
PRG::eval(seed, blocks.get(), static_cast<psnip_uint32_t>(nblocks),
static_cast<psnip_uint32_t>(start));
auto * bytes = reinterpret_cast<const unsigned char *>(blocks.get());
std::memcpy(out, bytes + static_cast<std::size_t>(byte_off % block_bytes),
static_cast<std::size_t>(nbytes));
}
};
template <typename PRG, typename T>
struct buffered_slot
{
using block_type = typename PRG::block_type;
explicit buffered_slot(block_type seed, std::size_t buffer_elems)
: seed_(seed),
buffer_(buffer_elems),
absolute_pos_(0u),
filled_(0u),
next_(0u)
{
refill(0u);
}
T get()
{
if (next_ >= filled_)
refill(absolute_pos_);
T v = buffer_[next_];
++next_;
++absolute_pos_;
return v;
}
void fill(T * out, std::size_t count)
{
std::size_t written = 0u;
while (written < count)
{
if (next_ >= filled_)
refill(absolute_pos_);
std::size_t available = filled_ - next_;
std::size_t take = available < count - written ? available : count - written;
std::memcpy(out + written, buffer_.data() + next_, take * sizeof(T));
written += take;
next_ += take;
absolute_pos_ += static_cast<std::uint64_t>(take);
}
}
T at(std::uint64_t index) const
{
return lane_codec<PRG, T>::at(seed_, index);
}
std::uint64_t sampled() const noexcept { return absolute_pos_; }
private:
void refill(std::uint64_t at_elem)
{
next_ = 0u;
filled_ = buffer_.size();
lane_codec<PRG, T>::fill(seed_, at_elem, buffer_.data(), buffer_.size());
}
block_type seed_;
std::vector<T> buffer_;
std::uint64_t absolute_pos_;
std::size_t filled_;
std::size_t next_;
};
} // namespace detail
template <typename PRG = dpf::prg::aes128>
typename PRG::block_type sample_master_seed()
{
return dpf::uniform_sample<typename PRG::block_type>();
}
/// Fixed lanes. Lane `I` is `PRG::eval(master, I)`.
template <typename PRG, typename... Ts>
class buffered_prg
{
public:
using prg_type = PRG;
using seed_type = typename PRG::block_type;
static constexpr std::size_t stream_count = sizeof...(Ts);
explicit buffered_prg(std::size_t per_stream_buffer_elems = 1024u)
: seed_(sample_master_seed<PRG>()),
buffers_(make_buffers(per_stream_buffer_elems))
{ }
explicit buffered_prg(seed_type seed, std::size_t per_stream_buffer_elems = 1024u)
: seed_(seed),
buffers_(make_buffers(per_stream_buffer_elems))
{ }
const seed_type & seed() const noexcept { return seed_; }
template <std::size_t I>
auto get()
{
static_assert(I < stream_count, "stream index out of range");
return std::get<I>(buffers_).get();
}
template <std::size_t I>
void fill(std::tuple_element_t<I, std::tuple<Ts...>> * out, std::size_t count)
{
static_assert(I < stream_count, "stream index out of range");
std::get<I>(buffers_).fill(out, count);
}
template <std::size_t I>
auto at(std::uint64_t index) const
{
static_assert(I < stream_count, "stream index out of range");
return std::get<I>(buffers_).at(index);
}
template <std::size_t I>
std::uint64_t sampled() const noexcept
{
static_assert(I < stream_count, "stream index out of range");
return std::get<I>(buffers_).sampled();
}
private:
template <std::size_t... I>
std::tuple<detail::buffered_slot<PRG, Ts>...>
make_buffers_impl(std::size_t n, std::index_sequence<I...>) const
{
return std::tuple<detail::buffered_slot<PRG, Ts>...>(
detail::buffered_slot<PRG, Ts>(
PRG::eval(seed_, static_cast<psnip_uint32_t>(I)), n)...);
}
std::tuple<detail::buffered_slot<PRG, Ts>...> make_buffers(std::size_t n) const
{
if (n == 0)
throw std::invalid_argument("per_stream_buffer_elems must be positive");
return make_buffers_impl(n, std::make_index_sequence<stream_count>{});
}
seed_type seed_;
std::tuple<detail::buffered_slot<PRG, Ts>...> buffers_;
};
template <typename... Ts>
using aes_buffered_prg = buffered_prg<dpf::prg::aes128, Ts...>;
/// Dynamic lanes of one value type. `value_at(role, index)` and
/// `mask_at(role, index)` are independent of call order. A window cache
/// refills from the requested index.
template <typename T, typename PRG = dpf::prg::aes128>
class lane_table
{
public:
using prg_type = PRG;
using seed_type = typename PRG::block_type;
using value_type = T;
explicit lane_table(std::size_t window = 256u)
: lane_table(sample_master_seed<PRG>(), window)
{ }
explicit lane_table(seed_type seed, std::size_t window = 256u)
: seed_(seed),
mask_seed_(detail::mask_master<PRG>(seed)),
window_(window)
{
if (window_ == 0)
throw std::invalid_argument("prg lane window must be positive");
}
lane_table(const lane_table &) = delete;
lane_table & operator=(const lane_table &) = delete;
lane_table(lane_table &&) = default;
lane_table & operator=(lane_table &&) = default;
const seed_type & seed() const noexcept { return seed_; }
T value_at(std::uint32_t role, std::uint64_t index) const
{
return cached(role, true, index);
}
T mask_at(std::uint32_t role, std::uint64_t index) const
{
return cached(role, false, index);
}
void fill_values(std::uint32_t role, std::uint64_t index, T * out, std::size_t n) const
{
if (n == 0)
return;
detail::lane_codec<PRG, T>::fill(slot(role).value_seed, index, out, n);
}
void fill_masks(std::uint32_t role, std::uint64_t index, T * out, std::size_t n) const
{
if (n == 0)
return;
detail::lane_codec<PRG, T>::fill(slot(role).mask_seed, index, out, n);
}
private:
using block_type = typename PRG::block_type;
struct slot_pair
{
block_type value_seed{};
block_type mask_seed{};
std::vector<T> value_cache;
std::vector<T> mask_cache;
std::uint64_t value_base = 0;
std::uint64_t mask_base = 0;
bool value_hot = false;
bool mask_hot = false;
};
slot_pair & slot(std::uint32_t role) const
{
auto it = slots_.find(role);
if (it != slots_.end())
return it->second;
slot_pair created;
created.value_seed = PRG::eval(seed_, role);
created.mask_seed = PRG::eval(mask_seed_, role);
created.value_cache.resize(window_);
created.mask_cache.resize(window_);
auto inserted = slots_.emplace(role, std::move(created));
return inserted.first->second;
}
T cached(std::uint32_t role, bool values, std::uint64_t index) const
{
slot_pair & s = slot(role);
std::vector<T> & buf = values ? s.value_cache : s.mask_cache;
std::uint64_t & base = values ? s.value_base : s.mask_base;
bool & hot = values ? s.value_hot : s.mask_hot;
block_type stream = values ? s.value_seed : s.mask_seed;
if (hot && index >= base && index < base + buf.size())
return buf[static_cast<std::size_t>(index - base)];
base = index;
hot = true;
detail::lane_codec<PRG, T>::fill(stream, index, buf.data(), buf.size());
return buf[0];
}
seed_type seed_;
seed_type mask_seed_;
std::size_t window_;
mutable std::map<std::uint32_t, slot_pair> slots_;
};
} // namespace randomness
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_BUFFERED_PRG_HPP__

352
include/dpf/dcf.hpp Normal file
View file

@ -0,0 +1,352 @@
/// @file dpf/dcf.hpp
/// @brief Comparison-channel specs and GGM path-sum helpers for libdpf.
/// @details `lt`/`leq`/`gt`/`geq` (+ `_at`) take `(if_true, if_false=0)`.
/// Eval walks the same GGM tree as the DPF (per-level value CWs).
/// `eq` / `eq_at` are synonyms for ordinary point placements.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
#ifndef LIBDPF_INCLUDE_DPF_DCF_HPP__
#define LIBDPF_INCLUDE_DPF_DCF_HPP__
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#include <limits>
#include "hedley/hedley.h"
#include "simde/simde/x86/avx2.h"
#include "dpf/utils.hpp"
#include "dpf/bit.hpp"
#include "dpf/xor_wrapper.hpp"
#include "dpf/twiddle.hpp"
namespace dpf
{
/// Comparison kind for the optional DCF channel on a key.
enum class cmp_kind : uint8_t
{
lt = 0,
leq = 1,
gt = 2,
geq = 3
};
enum class cmp_trivial : uint8_t
{
none = 0,
always_true = 1,
always_false = 2
};
namespace detail
{
namespace dcf_impl
{
template <typename Beta>
Beta default_false() noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return dpf::bit::zero;
else
return Beta{};
}
template <typename Beta>
uint64_t beta_delta_u64(const Beta & if_true, const Beta & if_false,
uint64_t mask) noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
{
const uint64_t t = static_cast<bool>(if_true) ? 1ULL : 0ULL;
const uint64_t f = static_cast<bool>(if_false) ? 1ULL : 0ULL;
return (t ^ f) & mask;
}
else if constexpr (dpf::utils::is_xor_wrapper_v<Beta>)
{
return (static_cast<uint64_t>(if_true) ^ static_cast<uint64_t>(if_false))
& mask;
}
else
{
return (static_cast<uint64_t>(if_true)
- static_cast<uint64_t>(if_false)) & mask;
}
}
template <typename Beta>
uint64_t beta_to_u64_simple(const Beta & beta, uint64_t mask) noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return (static_cast<bool>(beta) ? 1ULL : 0ULL) & mask;
else
return static_cast<uint64_t>(beta) & mask;
}
template <typename Beta>
Beta sub_beta(const Beta & a, const Beta & b) noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return dpf::bit{static_cast<bool>(a) ^ static_cast<bool>(b)};
else if constexpr (dpf::utils::is_xor_wrapper_v<Beta>)
return Beta{static_cast<uint64_t>(a) ^ static_cast<uint64_t>(b)};
else
return static_cast<Beta>(a - b);
}
template <typename Beta>
Beta u64_to_beta(uint64_t v) noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return dpf::bit{static_cast<bool>(v & 1u)};
else
return static_cast<Beta>(v);
}
inline uint64_t default_mask_for_bits(std::size_t out_bits) noexcept
{
if (out_bits >= 64)
return ~0ULL;
if (out_bits == 0)
return 0ULL;
return (1ULL << out_bits) - 1ULL;
}
HEDLEY_ALWAYS_INLINE
uint64_t neg_m(uint64_t x, uint64_t mask) noexcept
{
return (0ULL - x) & mask;
}
HEDLEY_ALWAYS_INLINE
uint64_t sgn_m(uint8_t t1, uint64_t x, uint64_t mask) noexcept
{
return t1 ? neg_m(x, mask) : x;
}
/// Convert a GGM node to a group element (low 64 bits, control bits cleared).
HEDLEY_ALWAYS_INLINE
uint64_t convert_node(simde__m128i n, uint64_t mask) noexcept
{
return static_cast<uint64_t>(
simde_mm_cvtsi128_si64(dpf::unset_lo_2bits(n))) & mask;
}
/// Draw the group-width blind `r` used to split the `cmp_addend` share.
/// `sample` yields one interior block; only `popcount(mask)` live bits are
/// kept, so the blind (and thus the addend share) never needs a full padded
/// `uint64_t` on the wire. Dealer and Doerner–Shelat gen call this with the
/// same block source so their keys stay byte-identical (matched tapes).
template <typename BlockSampler>
HEDLEY_ALWAYS_INLINE
uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept
{
return convert_node(dpf::unset_lo_2bits(sample()), mask);
}
/// One level of value CW on GGM children. Updates running `Va`.
/// `ai` is the keep-path bit of the (effective) threshold.
inline uint64_t make_value_cw(simde__m128i c0L, simde__m128i c0R,
simde__m128i c1L, simde__m128i c1R, uint8_t t0, uint8_t t1, int ai,
uint64_t & Va, uint64_t beta, uint64_t mask) noexcept
{
(void)t0;
uint64_t v0K, v1K, v0Lo, v1Lo;
if (ai == 0)
{
v0K = convert_node(c0L, mask);
v1K = convert_node(c1L, mask);
v0Lo = convert_node(c0R, mask);
v1Lo = convert_node(c1R, mask);
}
else
{
v0K = convert_node(c0R, mask);
v1K = convert_node(c1R, mask);
v0Lo = convert_node(c0L, mask);
v1Lo = convert_node(c1L, mask);
}
uint64_t vcw = sgn_m(t1,
(v1Lo + neg_m(v0Lo, mask) + neg_m(Va, mask)) & mask, mask);
// Lose-left (ai==1) is the x<α diverge: plant β there.
if (ai == 1)
vcw = (vcw + sgn_m(t1, beta, mask)) & mask;
Va = (Va + neg_m(v1K, mask) + v0K + sgn_m(t1, vcw, mask)) & mask;
return vcw;
}
/// Final leaf value CW. `on_path` is the payload reconstructed when the query
/// stays on α's path through all levels (0 for strict lt/geq; β for leq/gt).
inline uint64_t make_final_cw(simde__m128i s0, simde__m128i s1, uint8_t t1,
uint64_t Va, uint64_t mask, uint64_t on_path = 0) noexcept
{
uint64_t c0 = convert_node(s0, mask);
uint64_t c1 = convert_node(s1, mask);
return sgn_m(t1,
(c1 + neg_m(c0, mask) + neg_m(Va, mask) + on_path) & mask, mask);
}
} // namespace dcf_impl
/// Comparison metadata on an incremental key (value CWs live on the key).
/// Payload δ = if_true − if_false is dealer-known and baked into `value_cw` /
/// `cw_last` only — never stored clear on the key (traditional DPF hiding).
/// The second output value (`if_false`) is held as a per-party additive share
/// on the key (`cmp_addend`), not as a public constant.
struct cmp_meta
{
int nbits = 0; // comparison prefix length
uint64_t mask = 0;
cmp_kind kind = cmp_kind::lt;
cmp_trivial trivial = cmp_trivial::none;
bool eval_as_ge = false; // invert path-sum (geq / gt)
bool include_eq = false; // plant δ on the α-path leaf (leq / gt)
bool active = false;
bool empty() const noexcept { return !active; }
};
/// Backward-compatible alias while call sites migrate.
using cmp_channel = cmp_meta;
} // namespace detail
// ---------------------------------------------------------------------------
// Comparison specs: lt/leq/gt/geq (+ _at)
// ---------------------------------------------------------------------------
template <cmp_kind Kind, typename Beta>
struct cmp_pack
{
static constexpr bool is_cmp = true;
static constexpr cmp_kind kind = Kind;
static constexpr std::size_t prefix = 0;
using beta_type = Beta;
Beta if_true;
Beta if_false;
explicit cmp_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
: if_true{std::move(t)}, if_false{std::move(f)} { }
};
template <std::size_t N, cmp_kind Kind, typename Beta>
struct cmp_at_pack
{
static constexpr bool is_cmp = true;
static constexpr cmp_kind kind = Kind;
static constexpr std::size_t prefix = N;
using beta_type = Beta;
Beta if_true;
Beta if_false;
explicit cmp_at_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
: if_true{std::move(t)}, if_false{std::move(f)} { }
};
template <typename Beta>
inline auto lt(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_pack<cmp_kind::lt, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <typename Beta>
inline auto leq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_pack<cmp_kind::leq, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <typename Beta>
inline auto gt(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_pack<cmp_kind::gt, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <typename Beta>
inline auto geq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_pack<cmp_kind::geq, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto lt_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_at_pack<N, cmp_kind::lt, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto leq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_at_pack<N, cmp_kind::leq, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto gt_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_at_pack<N, cmp_kind::gt, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto geq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_at_pack<N, cmp_kind::geq, std::decay_t<Beta>>(std::move(t), std::move(f));
}
// ---------------------------------------------------------------------------
// Equality specs: eq / eq_at
// ---------------------------------------------------------------------------
template <typename Beta>
struct eq_pack
{
static constexpr bool is_eq = true;
static constexpr std::size_t prefix = 0;
using beta_type = Beta;
Beta if_true;
Beta if_false;
explicit eq_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
: if_true{std::move(t)}, if_false{std::move(f)} { }
};
template <std::size_t N, typename Beta>
struct eq_at_pack
{
static constexpr bool is_eq = true;
static constexpr std::size_t prefix = N;
using beta_type = Beta;
Beta if_true;
Beta if_false;
explicit eq_at_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
: if_true{std::move(t)}, if_false{std::move(f)} { }
};
template <typename Beta>
inline auto eq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return eq_pack<std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto eq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return eq_at_pack<N, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <typename T> struct is_cmp_spec : std::false_type {};
template <cmp_kind K, typename B> struct is_cmp_spec<cmp_pack<K, B>> : std::true_type {};
template <std::size_t N, cmp_kind K, typename B>
struct is_cmp_spec<cmp_at_pack<N, K, B>> : std::true_type {};
template <typename T>
inline constexpr bool is_cmp_spec_v = is_cmp_spec<T>::value;
template <typename T> struct is_eq_spec : std::false_type {};
template <typename B> struct is_eq_spec<eq_pack<B>> : std::true_type {};
template <std::size_t N, typename B>
struct is_eq_spec<eq_at_pack<N, B>> : std::true_type {};
template <typename T>
inline constexpr bool is_eq_spec_v = is_eq_spec<T>::value;
template <typename T>
inline constexpr bool is_dcf_spec_v = is_cmp_spec_v<T>;
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_DCF_HPP__

View file

@ -0,0 +1,542 @@
/// @file dpf/doerner_shelat.hpp
/// @brief Doerner–Shelat generation of a dealer DPF key.
/// @details Two XOR shares of the point are walked level by level. Correction
/// words, advice bits, seeds, and leaves are the ones `make_dpf`
/// would emit for the XOR of those shares, the same roots, and the
/// same beaver coins. Beaver pads used to hide the path bit cancel
/// and are not part of the key. Pad randomness must not come from
/// `uniform_fill` if the beaver tape is being matched.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_DOERNER_SHELAT_HPP__
#define LIBDPF_INCLUDE_DPF_DOERNER_SHELAT_HPP__
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#include "hedley/hedley.h"
#include "simde/simde/x86/avx2.h"
#include "dpf/dpf_key.hpp"
#include "dpf/random.hpp"
#include "dpf/dcf.hpp"
namespace dpf
{
/// Roots and the Beaver-pad stream for one Doerner–Shelat generation.
/// `root` is called twice, same as `make_dpf`: party 0 clears the low bit of
/// the first sample, party 1 sets the low bit of the second.
template <typename RootSampler, typename PadRng>
struct ds_randomness
{
RootSampler root;
PadRng pad;
};
namespace detail
{
struct urandom_pad_rng
{
simde__m128i block()
{
return dpf::uniform_sample<simde__m128i>();
}
uint8_t bit()
{
return static_cast<uint8_t>(dpf::uniform_sample<unsigned char>() & 1u);
}
};
struct ds_cw_party
{
simde__m128i rand;
simde__m128i gamma;
uint8_t bit;
};
struct ds_cw_pads
{
ds_cw_party p0;
ds_cw_party p1;
};
struct ds_blind
{
simde__m128i msg;
uint8_t bit;
};
struct ds_and_pads
{
uint8_t a0;
uint8_t a1;
simde__m128i b0_share, b1_share, c0_share, c1_share;
};
struct ds_and_shares
{
simde__m128i z0;
simde__m128i z1;
};
HEDLEY_ALWAYS_INLINE
simde__m128i ds_xor(simde__m128i a, simde__m128i b) noexcept
{
return simde_mm_xor_si128(a, b);
}
HEDLEY_ALWAYS_INLINE
simde__m128i ds_gate(uint8_t bit, simde__m128i block) noexcept
{
return dpf::get_if(block, bit & 1u);
}
template <typename PadRng>
ds_cw_pads ds_sample_cw(PadRng & pad)
{
ds_cw_pads p{};
const simde__m128i zero = simde_mm_setzero_si128();
p.p0.rand = pad.block();
p.p1.rand = pad.block();
p.p0.bit = static_cast<uint8_t>(pad.bit() & 1u);
p.p1.bit = static_cast<uint8_t>(pad.bit() & 1u);
p.p0.gamma = p.p1.bit ? p.p0.rand : zero;
p.p1.gamma = p.p0.bit ? p.p1.rand : zero;
return p;
}
template <typename PadRng>
ds_and_pads ds_sample_and(PadRng & pad)
{
ds_and_pads p{};
const uint8_t a = static_cast<uint8_t>(pad.bit() & 1u);
const simde__m128i B = pad.block();
const simde__m128i C = ds_gate(a, B);
p.a0 = static_cast<uint8_t>(pad.bit() & 1u);
p.a1 = static_cast<uint8_t>(a ^ p.a0);
p.b0_share = pad.block();
p.b1_share = ds_xor(B, p.b0_share);
p.c0_share = pad.block();
p.c1_share = ds_xor(C, p.c0_share);
return p;
}
HEDLEY_ALWAYS_INLINE
simde__m128i ds_cw_share(simde__m128i L, simde__m128i R, uint8_t my_bit,
const ds_cw_party & mine, const ds_blind & their) noexcept
{
simde__m128i out = ds_xor(R, mine.gamma);
if (my_bit & 1u)
{
out = ds_xor(out, ds_xor(ds_xor(L, R), their.msg));
}
if (their.bit & 1u)
{
out = ds_xor(out, mine.rand);
}
return out;
}
inline void ds_cw_blinds(const ds_cw_pads & p,
simde__m128i L0, simde__m128i R0, uint8_t bit0,
simde__m128i L1, simde__m128i R1, uint8_t bit1,
ds_blind & b0, ds_blind & b1) noexcept
{
b0.bit = static_cast<uint8_t>(bit0 ^ p.p0.bit);
b1.bit = static_cast<uint8_t>(bit1 ^ p.p1.bit);
b0.msg = ds_xor(ds_xor(L0, R0), p.p0.rand);
b1.msg = ds_xor(ds_xor(L1, R1), p.p1.rand);
}
inline simde__m128i ds_cw_outs(const ds_cw_pads & p,
simde__m128i L0, simde__m128i R0, uint8_t bit0,
simde__m128i L1, simde__m128i R1, uint8_t bit1,
const ds_blind & b0, const ds_blind & b1) noexcept
{
return ds_xor(
ds_cw_share(L0, R0, bit0, p.p0, b1),
ds_cw_share(L1, R1, bit1, p.p1, b0));
}
inline uint8_t ds_open_advice(simde__m128i L0, simde__m128i R0, uint8_t bit0,
simde__m128i L1, simde__m128i R1, uint8_t bit1) noexcept
{
const uint8_t a00 = static_cast<uint8_t>(dpf::get_lo_bit(L0) ^ bit0);
const uint8_t a01 = static_cast<uint8_t>(dpf::get_lo_bit(R0) ^ bit0);
const uint8_t a10 = static_cast<uint8_t>(dpf::get_lo_bit(L1) ^ bit1);
const uint8_t a11 = static_cast<uint8_t>(dpf::get_lo_bit(R1) ^ bit1);
const uint8_t t0 = static_cast<uint8_t>(a00 ^ a10 ^ 1u);
const uint8_t t1 = static_cast<uint8_t>(a01 ^ a11);
return static_cast<uint8_t>((t1 << 1) | (t0 & 1u));
}
inline void ds_next_terms(simde__m128i L, simde__m128i R, uint8_t advice,
simde__m128i cw, uint8_t tpack, simde__m128i & M, simde__m128i & base) noexcept
{
const simde__m128i D = ds_xor(L, R);
const uint8_t t0 = static_cast<uint8_t>(tpack & 1u);
const uint8_t t1 = static_cast<uint8_t>((tpack >> 1) & 1u);
const simde__m128i lo = dpf::set_lo_bit(simde_mm_setzero_si128(), 1);
const simde__m128i DT = ds_gate(static_cast<uint8_t>(t0 ^ t1), lo);
const simde__m128i cw_base = ds_xor(dpf::unset_lo_bit(cw), ds_gate(t0, lo));
M = (advice & 1u) ? ds_xor(D, DT) : D;
base = (advice & 1u) ? ds_xor(L, cw_base) : L;
}
inline ds_and_shares ds_and_open(const ds_and_pads & p, simde__m128i M,
uint8_t b_recv) noexcept
{
const simde__m128i e = ds_xor(ds_xor(M, p.b0_share), p.b1_share);
const uint8_t d = static_cast<uint8_t>((b_recv ^ p.a1) ^ p.a0);
ds_and_shares z;
z.z0 = ds_xor(ds_xor(ds_xor(ds_gate(d, e), ds_gate(d, p.b0_share)),
ds_gate(p.a0, e)), p.c0_share);
z.z1 = ds_xor(ds_xor(ds_gate(d, p.b1_share), ds_gate(p.a1, e)), p.c1_share);
return z;
}
inline simde__m128i ds_deliver(uint8_t b_exp, simde__m128i base, simde__m128i M,
const ds_and_shares & z) noexcept
{
const simde__m128i local = ds_xor(base, ds_gate(b_exp, M));
return ds_xor(ds_xor(local, z.z0), z.z1);
}
/// Per-level messages prepared before the CW protocol runs (blinds + pads).
struct ds_level_blinds
{
ds_cw_pads cwp;
ds_and_pads and0;
ds_and_pads and1;
ds_blind b0;
ds_blind b1;
simde__m128i L0, R0, L1, R1;
uint8_t bit0;
uint8_t bit1;
};
/// Opened CW, advice, and AND products delivered by a `CwProtocol`.
struct ds_level_open
{
simde__m128i cw;
uint8_t advice;
ds_and_shares z0;
ds_and_shares z1;
uint64_t value_cw = 0; // public after open when cmp is active at this level
};
/// Running comparison-gen state shared across DS levels (Va residual).
/// When `track_coeff` is set (wildcard cmp payload), a parallel β = 1
/// accumulator `Va1` is advanced alongside `Va` so the gen can stash
/// `value_cw(1) − value_cw(β)` coefficients for a later `assign_cmp`.
struct ds_cmp_gen_state
{
bool active = false;
std::size_t nbits = 0;
uint64_t mask = 0;
uint64_t beta = 0;
bool include_eq = false;
cmp_trivial trivial = cmp_trivial::none;
uint64_t Va = 0;
unsigned __int128 thresh = 0;
bool track_coeff = false;
uint64_t Va1 = 0;
uint64_t last_vcw_coeff = 0;
};
/// Local joint simulation: today's `ds_cw_outs` / `ds_open_advice` / `ds_and_open`.
/// An MPC backend would send `blinds` and return the same `ds_level_open` shape.
template <typename PadRng>
struct local_cw_protocol
{
PadRng & pads;
ds_level_blinds prepare_level(simde__m128i L0, simde__m128i R0, uint8_t bit0,
simde__m128i L1, simde__m128i R1, uint8_t bit1)
{
ds_level_blinds b;
b.cwp = ds_sample_cw(pads);
b.and0 = ds_sample_and(pads);
b.and1 = ds_sample_and(pads);
b.L0 = L0;
b.R0 = R0;
b.L1 = L1;
b.R1 = R1;
b.bit0 = bit0;
b.bit1 = bit1;
ds_cw_blinds(b.cwp, L0, R0, bit0, L1, R1, bit1, b.b0, b.b1);
return b;
}
ds_level_open complete_level(std::size_t /*level*/, const ds_level_blinds & b,
simde__m128i M0, simde__m128i M1, uint8_t rec0, uint8_t rec1)
{
ds_level_open out;
out.advice = ds_open_advice(b.L0, b.R0, b.bit0, b.L1, b.R1, b.bit1);
out.cw = ds_cw_outs(b.cwp, b.L0, b.R0, b.bit0, b.L1, b.R1, b.bit1,
b.b0, b.b1);
out.z0 = ds_and_open(b.and0, M0, rec0);
out.z1 = ds_and_open(b.and1, M1, rec1);
return out;
}
/// Open CW + advice only (AND pads stay in `blinds` for a later open).
std::pair<simde__m128i, uint8_t> open_cw(const ds_level_blinds & b) noexcept
{
return {ds_cw_outs(b.cwp, b.L0, b.R0, b.bit0, b.L1, b.R1, b.bit1,
b.b0, b.b1),
ds_open_advice(b.L0, b.R0, b.bit0, b.L1, b.R1, b.bit1)};
}
/// Open the public value CW for this level (local: clear convert+make_value_cw).
/// MPC backends open additive shares of the same word.
uint64_t open_value_cw(const ds_level_blinds & b, uint8_t adv0, uint8_t adv1,
int ai, uint64_t & Va, uint64_t beta, uint64_t mask) noexcept
{
return dcf_impl::make_value_cw(b.L0, b.R0, b.L1, b.R1, adv0,
adv1, ai, Va, beta, mask);
}
ds_and_shares open_and(const ds_and_pads & p, simde__m128i M,
uint8_t b_recv) noexcept
{
return ds_and_open(p, M, b_recv);
}
/// Open the final comparison leaf CW. Wraps `make_final_cw` so the
/// Doerner–Shelat gen does not call it directly on reconstructed seeds;
/// an MPC backend would open additive shares of the same word.
uint64_t open_final_cw(simde__m128i s0, simde__m128i s1, uint8_t t1,
uint64_t Va, uint64_t mask, uint64_t on_path) noexcept
{
return dcf_impl::make_final_cw(s0, s1, t1, Va, mask, on_path);
}
/// Draw the group-width `cmp_addend` blind. Local joint simulation reuses
/// the shared root sampler so the blind matches the dealer's; an MPC
/// backend would instead pull a group-width element from the pad stream.
template <typename BlockSampler>
uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept
{
return dcf_impl::sample_addend_blind(mask,
std::forward<BlockSampler>(sample));
}
/// Open a group of leaf correction words for one prefix group. In this
/// local joint simulation both XOR shares of the point are present, so the
/// point is reconstructed *inside* the protocol and handed to `leaf_fn`
/// (which runs `make_leaves` for the group). The Doerner–Shelat gen never
/// forms `x = x0 ^ x1` at its own call site; an MPC backend would instead
/// run a per-group leaf CW exchange that never reveals `x`.
template <typename InputT, typename LeafFn>
void open_leaf_group(InputT x0, InputT x1, LeafFn && leaf_fn)
{
std::forward<LeafFn>(leaf_fn)(utils::xor_input_shares(x0, x1));
}
};
/// Generation-side level state (seeds / home bits). Not an eval path memoizer.
template <typename NodeT>
struct ds_gen_state
{
NodeT inbox[2];
int home[2];
NodeT root0;
NodeT root1;
void init(NodeT r0, NodeT r1) noexcept
{
root0 = r0;
root1 = r1;
inbox[0] = r0;
inbox[1] = r1;
home[0] = 0;
home[1] = 1;
}
NodeT & seed0() noexcept { return inbox[home[0]]; }
NodeT & seed1() noexcept { return inbox[home[1]]; }
const NodeT & seed0() const noexcept { return inbox[home[0]]; }
const NodeT & seed1() const noexcept { return inbox[home[1]]; }
};
/// One interior level: expand, protocol open, advance both party seeds.
/// When `cmp` is non-null and active for `level`, also opens `value_cw` via
/// the protocol (no second PRG expand outside).
template <typename InteriorPRG, typename CwProtocol, typename NodeT,
typename InputT, typename AdviceT>
void ds_advance_level(ds_gen_state<NodeT> & st, InputT x0, InputT x1,
InputT mask, std::size_t level, CwProtocol & proto, NodeT & cw_out,
AdviceT & advice_out, uint64_t * value_cw_out = nullptr,
ds_cmp_gen_state * cmp = nullptr)
{
// Integral bridge so bit extraction works for `keyword` / `modint` /
// signed / bitstring the same way dealer gen does via `mask & x`.
constexpr auto to_int = utils::to_integral_type<InputT>{};
const auto mi = to_int(mask);
const uint8_t bit0 = static_cast<uint8_t>(!!(mi & to_int(x0)));
const uint8_t bit1 = static_cast<uint8_t>(!!(mi & to_int(x1)));
NodeT s0 = st.seed0();
NodeT s1 = st.seed1();
const uint8_t adv0 = static_cast<uint8_t>(
dpf::get_lo_bit_and_clear_lo_2bits(s0));
const uint8_t adv1 = static_cast<uint8_t>(
dpf::get_lo_bit_and_clear_lo_2bits(s1));
const auto c0 = InteriorPRG::eval01(s0);
const auto c1 = InteriorPRG::eval01(s1);
auto blinds = proto.prepare_level(c0[0], c0[1], bit0, c1[0], c1[1], bit1);
if (value_cw_out != nullptr && cmp != nullptr && cmp->active
&& cmp->trivial == cmp_trivial::none && level < cmp->nbits)
{
const int ai = static_cast<int>(
(cmp->thresh >> (cmp->nbits - 1 - level)) & 1);
*value_cw_out = proto.open_value_cw(blinds, adv0, adv1, ai, cmp->Va,
cmp->beta, cmp->mask);
if (cmp->track_coeff)
{
// Affine coefficient: same level with β = 1 on a parallel Va.
const uint64_t v1 = proto.open_value_cw(blinds, adv0, adv1, ai,
cmp->Va1, 1ULL, cmp->mask);
cmp->last_vcw_coeff =
(v1 + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask;
}
}
auto [cw, tpack] = proto.open_cw(blinds);
const uint8_t exp0 = st.home[0] == 0 ? bit0 : bit1;
const uint8_t rec0 = st.home[0] == 0 ? bit1 : bit0;
const uint8_t exp1 = st.home[1] == 0 ? bit0 : bit1;
const uint8_t rec1 = st.home[1] == 0 ? bit1 : bit0;
NodeT M0, base0, M1, base1;
ds_next_terms(c0[0], c0[1], adv0, cw, tpack, M0, base0);
ds_next_terms(c1[0], c1[1], adv1, cw, tpack, M1, base1);
const NodeT nxt0 =
ds_deliver(exp0, base0, M0, proto.open_and(blinds.and0, M0, rec0));
const NodeT nxt1 =
ds_deliver(exp1, base1, M1, proto.open_and(blinds.and1, M1, rec1));
st.home[0] ^= 1;
st.home[1] ^= 1;
st.inbox[st.home[0]] = nxt0;
st.inbox[st.home[1]] = nxt1;
cw_out = cw;
advice_out = tpack;
}
template <typename T>
struct is_ds_randomness : std::false_type {};
template <typename RootSampler, typename PadRng>
struct is_ds_randomness<ds_randomness<RootSampler, PadRng>> : std::true_type {};
template <typename T, typename = void>
struct is_cw_protocol : std::false_type {};
template <typename PadRng>
struct is_cw_protocol<local_cw_protocol<PadRng>, void> : std::true_type {};
template <typename T>
struct is_cw_protocol<T,
std::void_t<decltype(std::declval<T &>().prepare_level(
simde_mm_setzero_si128(), simde_mm_setzero_si128(),
uint8_t{}, simde_mm_setzero_si128(),
simde_mm_setzero_si128(), uint8_t{})),
decltype(std::declval<T &>().open_cw(
std::declval<const ds_level_blinds &>()))>>
: std::true_type {};
template <typename ...Ts>
struct first_is_cw_protocol : std::false_type {};
template <typename T, typename ...Rest>
struct first_is_cw_protocol<T, Rest...>
: is_cw_protocol<std::decay_t<T>> {};
template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs,
typename RootSampler,
typename CwProtocol>
auto make_dpf_doerner_shelat_impl(InputT x0, InputT x1,
RootSampler & root_sampler, CwProtocol & proto, OutputT && y,
OutputTs && ...ys)
{
static_assert(!dpf::is_wildcard_v<InputT>,
"Doerner–Shelat gen takes XOR shares of a concrete point");
static_assert(!dpf::is_secret_share_v<InputT>,
"Doerner–Shelat: pass additive_share of xor_wrapper, or raw XOR shares");
static_assert(sizeof(typename InteriorPRG::block_type) == sizeof(simde__m128i),
"Doerner–Shelat gen uses the AES-block interior node");
using dpf_type = utils::dpf_type_t<InteriorPRG, ExteriorPRG, InputT,
OutputT, OutputTs...>;
using node = typename dpf_type::interior_node;
using input_type = typename dpf_type::input_type;
constexpr auto depth = dpf_type::depth;
utils::flip_msb_if_signed_integral(x0);
const node root0 = dpf::unset_lo_bit(static_cast<node>(root_sampler()));
const node root1 = dpf::set_lo_bit(static_cast<node>(root_sampler()));
ds_gen_state<node> st;
st.init(root0, root1);
typename dpf_type::correction_words_array correction_words{};
typename dpf_type::correction_advice_array correction_advice{};
auto mask = dpf_type::msb_mask;
for (std::size_t level = 0; level < depth; ++level, mask >>= 1)
{
ds_advance_level<InteriorPRG>(st, x0, x1, mask, level, proto,
correction_words[level], correction_advice[level]);
}
const node parent0 = st.seed0();
const node parent1 = st.seed1();
const bool sign0 = dpf::get_lo_bit(parent0);
input_type x = utils::xor_input_shares(x0, x1);
auto built = dpf::make_leaves<ExteriorPRG>(x,
dpf::unset_lo_2bits(parent0), dpf::unset_lo_2bits(parent1), sign0,
std::size_t{0}, std::forward<OutputT>(y), std::forward<OutputTs>(ys)...);
input_type off0{};
input_type off1{};
return dpf::make_party_key_pair(
dpf_type{root0, correction_words, correction_advice,
built.first.first, built.first.second, off0},
dpf_type{root1, correction_words, correction_advice,
built.second.first, built.second.second, off1});
}
} // namespace detail
/// Local CW protocol (pads cancel; same keys as dealer when roots match).
template <typename PadRng>
using local_cw_protocol = detail::local_cw_protocol<PadRng>;
template <typename NodeT>
using ds_gen_state = detail::ds_gen_state<NodeT>;
// Public `make_dpf_doerner_shelat(x0, x1, ...)` lives in incremental.hpp so
// classic and `at<>` / mixed-width packs share one entry point.
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_DOERNER_SHELAT_HPP__

1192
include/dpf/dpf_key.hpp Normal file

File diff suppressed because it is too large Load diff

252
include/dpf/emplace.hpp Normal file
View file

@ -0,0 +1,252 @@
/// @file dpf/emplace.hpp
/// @brief Defines various template structures for efficient emplacement of
/// `dpf::dpf_key` objects.
/// @details Provides specialized template structures for the in-place
/// construction ("emplacing") of `dpf::dpf_key` objects into different
/// types of pre-allocated storage including smart pointers
/// (`std::unique_ptr` and `std::shared_ptr`), `std::optional`,
/// `std::variant`, raw pointers, and `std::reference_wrapper`s, as
/// well as containers that support `emplace_back`. The goal is to
/// facilitate efficient construction and storage of `dpf::dpf_key`
/// objects received from a dealer over a socket.
///
/// The emplacement functionalities are specialized for different
/// storage types to handle their unique construction requirements.
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_EMPLACE_HPP__
#define LIBDPF_INCLUDE_DPF_EMPLACE_HPP__
#include "hedley/hedley.h"
#include <variant>
#include <memory>
#include <optional>
#include <functional>
namespace dpf
{
namespace utils
{
/// @brief Emplaces a `dpf::dpf_key` object into the specified, pre-allocated memory.
/// @tparam DpfKey The concrete specialization of `dpf::dpf_key` to construct.
/// @param storage Reference to the container where the `dpf::dpf_key` object will be emplaced.
/// @param root The root node used by the `dpf::dpf_key`.
/// @param correction_words Correction words array for the `dpf::dpf_key`.
/// @param correction_advice Correction advice array for the `dpf::dpf_key`.
/// @param leaves Leaf-node tuple for the `dpf::dpf_key`.
/// @param beavers Beaver tuple for the `dpf::dpf_key`.
/// @param offset_share The offset share (default: `0`).
/// @defgroup EmplaceFunctions Emplace Functions
/// @brief Group of functions for emplacing `dpf::dpf_key` objects into
/// pre-allocated memory.
/// @{
template <typename DpfKey, typename T>
struct dpf_emplacer
{
using dpf_key = DpfKey;
using interior_node = typename DpfKey::interior_node;
using correction_words_array = typename DpfKey::correction_words_array;
using correction_advice_array = typename DpfKey::correction_advice_array;
using leaf_tuple = typename DpfKey::leaf_tuple;
using beaver_tuple = typename DpfKey::beaver_tuple;
using input_type = typename DpfKey::input_type;
/// @brief Generic version is intentionally left undefined.
static auto emplace(T & storage,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share);
};
/// @brief Specialization for `std::unique_ptr`.
template <typename DpfKey>
struct dpf_emplacer<DpfKey, std::unique_ptr<DpfKey>>
{
using dpf_key = DpfKey;
using interior_node = typename DpfKey::interior_node;
using correction_words_array = typename DpfKey::correction_words_array;
using correction_advice_array = typename DpfKey::correction_advice_array;
using leaf_tuple = typename DpfKey::leaf_tuple;
using beaver_tuple = typename DpfKey::beaver_tuple;
using input_type = typename DpfKey::input_type;
HEDLEY_ALWAYS_INLINE
static auto emplace(
std::unique_ptr<DpfKey> & storage,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
storage.reset(new DpfKey(root, correction_words, correction_advice, leaves, beavers, offset_share));
}
};
template <typename DpfKey>
struct dpf_emplacer<DpfKey, std::shared_ptr<DpfKey>>
{
using dpf_key = DpfKey;
using interior_node = typename DpfKey::interior_node;
using correction_words_array = typename DpfKey::correction_words_array;
using correction_advice_array = typename DpfKey::correction_advice_array;
using leaf_tuple = typename DpfKey::leaf_tuple;
using beaver_tuple = typename DpfKey::beaver_tuple;
using input_type = typename DpfKey::input_type;
HEDLEY_ALWAYS_INLINE
static auto emplace(
std::shared_ptr<DpfKey> & storage,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
storage = std::make_shared<DpfKey>(root, correction_words, correction_advice, leaves, beavers, offset_share);
}
};
template <typename DpfKey>
struct dpf_emplacer<DpfKey, std::optional<DpfKey>>
{
using dpf_key = DpfKey;
using interior_node = typename DpfKey::interior_node;
using correction_words_array = typename DpfKey::correction_words_array;
using correction_advice_array = typename DpfKey::correction_advice_array;
using leaf_tuple = typename DpfKey::leaf_tuple;
using beaver_tuple = typename DpfKey::beaver_tuple;
using input_type = typename DpfKey::input_type;
HEDLEY_ALWAYS_INLINE
static auto emplace(
std::optional<DpfKey> & storage,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
storage.emplace(root, correction_words, correction_advice, leaves, beavers, offset_share);
}
};
template <typename DpfKey, typename ...Ts>
struct dpf_emplacer<DpfKey, std::variant<Ts...>>
{
using dpf_key = DpfKey;
using interior_node = typename DpfKey::interior_node;
using correction_words_array = typename DpfKey::correction_words_array;
using correction_advice_array = typename DpfKey::correction_advice_array;
using leaf_tuple = typename DpfKey::leaf_tuple;
using beaver_tuple = typename DpfKey::beaver_tuple;
using input_type = typename DpfKey::input_type;
HEDLEY_ALWAYS_INLINE
static auto emplace(
std::variant<Ts...> & storage,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
storage.template emplace<DpfKey>(root, correction_words, correction_advice, leaves, beavers, offset_share);
}
};
template <typename DpfKey>
struct dpf_emplacer<DpfKey, DpfKey *>
{
using dpf_key = DpfKey;
using interior_node = typename DpfKey::interior_node;
using correction_words_array = typename DpfKey::correction_words_array;
using correction_advice_array = typename DpfKey::correction_advice_array;
using leaf_tuple = typename DpfKey::leaf_tuple;
using beaver_tuple = typename DpfKey::beaver_tuple;
using input_type = typename DpfKey::input_type;
HEDLEY_ALWAYS_INLINE
static auto emplace(
DpfKey * storage,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
::new (storage) DpfKey(root, correction_words, correction_advice, leaves, beavers, offset_share);
}
};
template <typename DpfKey>
struct dpf_emplacer<DpfKey, std::reference_wrapper<DpfKey>>
{
using dpf_key = DpfKey;
using interior_node = typename DpfKey::interior_node;
using correction_words_array = typename DpfKey::correction_words_array;
using correction_advice_array = typename DpfKey::correction_advice_array;
using leaf_tuple = typename DpfKey::leaf_tuple;
using beaver_tuple = typename DpfKey::beaver_tuple;
using input_type = typename DpfKey::input_type;
HEDLEY_ALWAYS_INLINE
static auto emplace(
std::reference_wrapper<DpfKey> storage,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
storage.get().~DpfKey();
::new (&storage.get()) DpfKey(root, correction_words, correction_advice, leaves, beavers, offset_share);
}
};
/// @}
template <typename DpfKey, typename ContainerT>
struct dpf_back_emplacer
{
using dpf_key = DpfKey;
using interior_node = typename DpfKey::interior_node;
using correction_words_array = typename DpfKey::correction_words_array;
using correction_advice_array = typename DpfKey::correction_advice_array;
using leaf_tuple = typename DpfKey::leaf_tuple;
using beaver_tuple = typename DpfKey::beaver_tuple;
using input_type = typename DpfKey::input_type;
static auto emplace_back(ContainerT & storage,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
storage.emplace_back(root, correction_words, correction_advice, leaves, beavers, offset_share);
}
};
} // namespace utils
} // namespace std
#endif // LIBDPF_INCLUDE_DPF_EMPLACE_HPP__

169
include/dpf/eval_common.hpp Normal file
View file

@ -0,0 +1,169 @@
/// @file dpf/eval_common.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_EVAL_COMMON_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_COMMON_HPP__
#include "hedley/hedley.h"
#include <cstddef>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include "dpf/leaf_node.hpp"
#include "dpf/secret_share.hpp"
namespace dpf
{
/// Sentinel: `dpf_output` converts to a bare `OutputT` (no party tag).
inline constexpr std::size_t no_party = std::numeric_limits<std::size_t>::max();
/// Eval result type for a leaf output of `KeyT`: subtractive share when the
/// key is party-tagged, otherwise the concrete output type.
template <typename KeyT, typename OutputT, bool = is_party_key_v<KeyT>>
struct eval_leaf_result
{
using type = OutputT;
};
template <typename KeyT, typename OutputT>
struct eval_leaf_result<KeyT, OutputT, true>
{
using type = subtractive_share<OutputT, party_of_v<KeyT>>;
};
template <typename KeyT, typename OutputT>
using eval_leaf_result_t = typename eval_leaf_result<KeyT, OutputT>::type;
/// Eval result type for a comparison output of `KeyT`: additive share when
/// the key is party-tagged, otherwise `Beta`.
template <typename KeyT, typename Beta, bool = is_party_key_v<KeyT>>
struct eval_cmp_result
{
using type = Beta;
};
template <typename KeyT, typename Beta>
struct eval_cmp_result<KeyT, Beta, true>
{
using type = additive_share<Beta, party_of_v<KeyT>>;
};
template <typename KeyT, typename Beta>
using eval_cmp_result_t = typename eval_cmp_result<KeyT, Beta>::type;
template <typename OutputT,
typename NodeT,
std::size_t Party = no_party>
struct alignas(utils::max_align_v) dpf_output
{
using result_type = std::conditional_t<
Party == no_party,
OutputT,
subtractive_share<OutputT, Party>>;
dpf_output(const dpf_output &) = default;
dpf_output(dpf_output &&) noexcept = default;
dpf_output & operator=(const dpf_output &) = default;
dpf_output & operator=(dpf_output &&) noexcept = default;
~dpf_output() = default;
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
constexpr operator result_type() const
{
OutputT v = extract_leaf<NodeT, OutputT>(node, offset);
if constexpr (Party == no_party)
return v;
else
return subtractive_share<OutputT, Party>::from_raw(v);
}
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
auto operator*() const
{
return static_cast<result_type>(*this);
}
NodeT node;
std::size_t offset;
private:
dpf_output(NodeT leaf_node, std::size_t off)
: node{leaf_node}, offset{off} { }
public:
template <typename Output,
typename Input,
typename Node>
friend auto make_dpf_output(const Node & node, Input x);
template <std::size_t P,
typename Output,
typename Input,
typename Node>
friend auto make_dpf_output(const Node & node, Input x);
};
/// Copy one packed leaf into a buffer whose element type may differ
/// from `LeafT` (bit arrays store `word_type`, not the exterior node).
/// Byte destination keeps the store free of strict-aliasing UB.
template <typename LeafT, typename Buffer>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
void store_leaf_bytes(Buffer && buf, std::size_t index, const LeafT & leaf) noexcept
{
auto * dst = reinterpret_cast<unsigned char *>(utils::data(buf))
+ index * sizeof(LeafT);
std::memcpy(dst, std::addressof(leaf), sizeof(LeafT));
}
template <typename Output,
typename Input,
typename Node>
auto make_dpf_output(const Node & node, Input x)
{
return dpf_output<concrete_type_t<Output>, Node>{node,
offset_within_block<concrete_type_t<Output>, Node>(x)};
}
template <std::size_t Party,
typename Output,
typename Input,
typename Node>
auto make_dpf_output(const Node & node, Input x)
{
return dpf_output<concrete_type_t<Output>, Node, Party>{node,
offset_within_block<concrete_type_t<Output>, Node>(x)};
}
/// Wrap a raw leaf node into a party-tagged `dpf_output` when `KeyT` is a
/// `party_key`, otherwise a bare `dpf_output`.
template <typename KeyT, typename Output, typename Input, typename Node>
auto make_eval_dpf_output(const Node & node, Input x)
{
if constexpr (is_party_key_v<KeyT>)
return make_dpf_output<party_of_v<KeyT>, Output>(node, x);
else
return make_dpf_output<Output>(node, x);
}
/// Wrap a raw comparison `Beta` value as an additive share when `KeyT` is a
/// `party_key`.
template <typename KeyT, typename Beta>
auto make_eval_cmp_result(Beta raw) noexcept
{
if constexpr (is_party_key_v<KeyT>)
return additive_share<Beta, party_of_v<KeyT>>::from_raw(raw);
else
return raw;
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_COMMON_HPP__

152
include/dpf/eval_full.hpp Normal file
View file

@ -0,0 +1,152 @@
/// @file dpf/eval_full.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @author Christopher Jiang <christopher.jiang@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_EVAL_FULL_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_FULL_HPP__
#include <portable-snippets/builtin/builtin.h>
#include "hedley/hedley.h"
#include <cstddef>
#include <type_traits>
#include <utility>
#include <limits>
#include "dpf/dpf_key.hpp"
#include "dpf/eval_common.hpp"
#include "dpf/eval_target.hpp"
#include "dpf/output_buffer.hpp"
#include "dpf/interval_memoizer.hpp"
#include "dpf/rotation_iterable.hpp"
#include "dpf/subinterval_iterable.hpp"
namespace dpf
{
namespace internal
{
template <std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename IntervalMemoizer,
std::size_t ...IIs,
std::enable_if_t<dpf::is_wildcard_v<typename DpfKey::raw_input_type>, bool> = false>
auto eval_full(const DpfKey & dpf, OutputBuffers && outbufs,
IntervalMemoizer && memoizer, std::index_sequence<IIs...>)
{
using dpf_type = DpfKey;
using input_type = typename dpf_type::input_type;
auto offset = dpf.offset_x(0); // N.B.: throws if dpf is not ready
dpf::internal::eval_interval_impl<Is...>(dpf,
std::numeric_limits<input_type>::min(),
std::numeric_limits<input_type>::max(),
outbufs, memoizer, std::make_index_sequence<sizeof...(Is)>());
return utils::make_tuple(dpf::rotation_iterable(std::begin(utils::get<IIs>(outbufs)), std::end(utils::get<IIs>(outbufs)), offset)...);
}
template <std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename IntervalMemoizer,
std::size_t ...IIs,
std::enable_if_t<!dpf::is_wildcard_v<typename DpfKey::raw_input_type>, bool> = false>
auto eval_full(const DpfKey & dpf, OutputBuffers && outbufs,
IntervalMemoizer && memoizer, std::index_sequence<IIs...>)
{
using dpf_type = DpfKey;
using input_type = typename dpf_type::input_type;
dpf::internal::eval_interval_impl<Is...>(dpf,
std::numeric_limits<input_type>::min(),
std::numeric_limits<input_type>::max(),
outbufs, memoizer, std::make_index_sequence<sizeof...(Is)>());
return utils::make_tuple(
subinterval_iterable(std::begin(utils::get<IIs>(outbufs)),
utils::size(utils::get<IIs>(outbufs)),
std::size_t{0},
utils::get<IIs>(outbufs).size() - 1,
std::size_t{0},
std::size_t{0})...);
}
} // namespace internal
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_full(const DpfKey & dpf, OutputBuffers && outbufs,
IntervalMemoizer && memoizer)
{
assert_not_wildcard_output<I, Is...>(dpf);
return internal::eval_full<I, Is...>(dpf, outbufs, memoizer, std::make_index_sequence<1+sizeof...(Is)>());
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<!std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<OutputBuffers>>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_full(const DpfKey & dpf, OutputBuffers & outbufs) // NOLINT(runtime/references)
{
using input_type = typename DpfKey::input_type;
return eval_full<I, Is...>(dpf, outbufs,
dpf::make_basic_full_memoizer(dpf));
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<IntervalMemoizer>>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_full(const DpfKey & dpf,
IntervalMemoizer && memoizer)
{
auto outbufs = utils::make_tuple(
make_output_buffer_for_full<I>(dpf),
make_output_buffer_for_full<Is>(dpf)...);
// moving `outbufs` is allowed as the `outbufs` are `std::vectors`
// the underlying data remains on the heap
// and thus the data the iterable refers to is still valid
auto iterable = eval_full<I, Is...>(dpf, outbufs, memoizer);
return std::make_pair(std::move(outbufs), std::move(iterable));
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_full(const DpfKey & dpf)
{
using input_type = typename DpfKey::input_type;
return eval_full<I, Is...>(dpf,
dpf::make_basic_full_memoizer(dpf));
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_FULL_HPP__

View file

@ -0,0 +1,461 @@
/// @file dpf/eval_inner_product.hpp
/// @brief Full / interval DPF evaluation that reduces against a public
/// weight vector instead of materializing the output.
/// @details Same interior + batched exterior AES as `eval_interval`, but
/// each packed leaf is multiply-accumulated into a scalar:
/// additive outputs sum `DPF(x) * w[x]`, XOR outputs xor
/// `DPF(x) & w[x]`. A prepared memoizer skips the interior walk
/// so the tree can be expanded before the weights exist.
#ifndef LIBDPF_INCLUDE_DPF_EVAL_INNER_PRODUCT_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_INNER_PRODUCT_HPP__
#include <array>
#include <cstddef>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include "hedley/hedley.h"
#include <portable-snippets/exact-int/exact-int.h>
#include <simde/simde/x86/avx2.h>
#include "dpf/dpf_key.hpp"
#include "dpf/eval_interval.hpp"
#include "dpf/eval_target.hpp"
#include "dpf/leaf_node.hpp"
#include "dpf/twiddle.hpp"
#include "dpf/utils.hpp"
#include "dpf/xor_wrapper.hpp"
namespace dpf
{
namespace internal
{
template <typename T>
struct is_xor_wrapper : std::false_type {};
template <typename T>
struct is_xor_wrapper<dpf::xor_wrapper<T>> : std::true_type {};
template <typename T>
inline constexpr bool is_xor_wrapper_v = is_xor_wrapper<T>::value;
template <typename W>
HEDLEY_ALWAYS_INLINE
auto weight_as_u64(W && w, std::size_t i)
{
return static_cast<psnip_uint64_t>(w[i]);
}
HEDLEY_ALWAYS_INLINE
simde__m128i load_weight_pair_u64(psnip_uint64_t lo, psnip_uint64_t hi)
{
return simde_mm_set_epi64x(static_cast<int64_t>(hi),
static_cast<int64_t>(lo));
}
HEDLEY_ALWAYS_INLINE
simde__m128i mullo_epi64x2(simde__m128i a, simde__m128i b)
{
#if defined(__AVX512DQ__) && defined(__AVX512VL__)
return _mm_mullo_epi64(a, b);
#else
psnip_uint64_t av[2], bv[2];
std::memcpy(av, &a, sizeof(av));
std::memcpy(bv, &b, sizeof(bv));
av[0] *= bv[0];
av[1] *= bv[1];
simde__m128i r;
std::memcpy(&r, av, sizeof(r));
return r;
#endif
}
template <typename NodeT,
typename OutputsTuple,
std::size_t ...Is>
struct ip_prg_range
{
static constexpr std::size_t pos_min
= const_min_size<block_offset_of_leaf_v<Is, NodeT, OutputsTuple>...>::value;
static constexpr std::size_t pos_end
= const_max_size<(block_offset_of_leaf_v<Is, NodeT, OutputsTuple>
+ block_length_of_leaf_v<std::tuple_element_t<Is, OutputsTuple>, NodeT>)...>::value;
static constexpr std::size_t count = pos_end - pos_min;
};
template <typename OutputT>
struct ip_accum
{
using output_type = OutputT;
static constexpr bool xor_mode = is_xor_wrapper_v<OutputT>;
static constexpr bool simd64 = (sizeof(OutputT) == 8);
simde__m128i vacc = simde_mm_setzero_si128();
output_type scalar{};
template <typename LeafT, typename W>
HEDLEY_ALWAYS_INLINE
void mac(const LeafT & leaf, std::size_t base, std::size_t opl, W && w)
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
if constexpr (simd64 && std::is_same_v<LeafT, simde__m128i>)
HEDLEY_PRAGMA(GCC diagnostic pop)
{
if (HEDLEY_LIKELY(opl == 2))
{
simde__m128i ww = load_weight_pair_u64(
weight_as_u64(w, base),
weight_as_u64(w, base + 1));
if constexpr (xor_mode)
{
vacc = simde_mm_xor_si128(vacc,
simde_mm_and_si128(leaf, ww));
}
else
{
vacc = simde_mm_add_epi64(vacc, mullo_epi64x2(leaf, ww));
}
return;
}
}
for (std::size_t p = 0; p < opl; ++p)
{
output_type val;
if constexpr (utils::is_packed_subbyte_v<output_type>)
{
val = extract_leaf<std::remove_cv_t<LeafT>, output_type>(leaf, p);
}
else
{
std::memcpy(&val,
reinterpret_cast<const unsigned char *>(std::addressof(leaf))
+ p * sizeof(output_type),
sizeof(val));
}
const auto wt = weight_as_u64(w, base + p);
if constexpr (xor_mode)
{
scalar = output_type{static_cast<typename output_type::value_type>(
static_cast<psnip_uint64_t>(scalar)
^ (static_cast<psnip_uint64_t>(val) & wt))};
}
else if constexpr (utils::is_packed_subbyte_v<output_type>
&& !std::is_same_v<output_type, dpf::bit>)
{
constexpr unsigned mask
= (1u << utils::packed_lane_bits_v<output_type>) - 1u;
const auto wlane = static_cast<output_type>(
static_cast<unsigned>(wt) & mask);
scalar = scalar + val * wlane;
}
else
{
scalar = static_cast<output_type>(
static_cast<psnip_uint64_t>(scalar)
+ static_cast<psnip_uint64_t>(val) * wt);
}
}
}
HEDLEY_ALWAYS_INLINE
output_type finish() const
{
if constexpr (simd64)
{
psnip_uint64_t lanes[2];
std::memcpy(lanes, &vacc, sizeof(lanes));
if constexpr (xor_mode)
{
return output_type{static_cast<typename output_type::value_type>(
(lanes[0] ^ lanes[1])
^ static_cast<psnip_uint64_t>(scalar))};
}
else
{
return output_type{
lanes[0] + lanes[1]
+ static_cast<psnip_uint64_t>(scalar)};
}
}
return scalar;
}
};
template <std::size_t ...Is,
typename DpfKey,
typename Weights,
typename IntervalMemoizer,
typename IntegralT,
std::size_t ...IIs>
void eval_inner_product_exterior(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, Weights && weights, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>,
std::tuple<ip_accum<typename DpfKey::concrete_output_type<Is>>...> & accs,
std::size_t start = 0)
{
assert_not_wildcard_output<Is...>(dpf);
if (HEDLEY_UNLIKELY(to_node < from_node && to_node != IntegralT{0}))
throw std::runtime_error("to_node<from_node");
using node_type = typename DpfKey::exterior_node;
using outputs_tuple = typename DpfKey::concrete_outputs_tuple;
using range = ip_prg_range<node_type, outputs_tuple, Is...>;
constexpr std::size_t opl = DpfKey::outputs_per_leaf;
std::size_t nodes_in_interval = static_cast<std::size_t>(to_node - from_node);
// `to_node == 0` is the saturated exclusive end; the subtraction is the
// leaf count. A real inverted range is rejected above.
auto *nodes = memoizer[DpfKey::depth];
auto cws = std::make_tuple(std::get<Is>(dpf.leaf_nodes).get()...);
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto apply_masks = [&](std::size_t k, const node_type & node,
const node_type * HEDLEY_RESTRICT masks)
{
auto apply_output = [&](auto out_index, auto buf_index)
{
constexpr std::size_t out_i = decltype(out_index)::value;
constexpr std::size_t buf_i = decltype(buf_index)::value;
using output_type = typename DpfKey::concrete_output_type<out_i>;
using leaf_type = dpf::leaf_node_t<node_type, output_type>;
constexpr auto pos = block_offset_of_leaf_v<out_i, node_type, outputs_tuple>;
leaf_type mask;
std::memcpy(&mask, masks + (pos - range::pos_min), sizeof(leaf_type));
// Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β.
auto leaf = dpf::subtract_leaf<output_type>(
get_if_lo_bit(std::get<buf_i>(cws), node), mask);
std::get<buf_i>(accs).mac(leaf, k * opl, opl,
utils::get<buf_i>(weights));
};
(apply_output(std::integral_constant<std::size_t, Is>{},
std::integral_constant<std::size_t, IIs>{}), ...);
};
std::size_t j = 0, k = start;
if constexpr (range::count == 2 && range::pos_min == 0)
{
for (; j + 4 <= nodes_in_interval; j += 4, k += 4)
{
alignas(node_type) node_type seeds[4];
alignas(node_type) node_type left[4];
alignas(node_type) node_type right[4];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
DpfKey::exterior_prg::eval01_x4(seeds, left, right);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
node_type masks[2] = {left[t], right[t]};
apply_masks(k + t, nodes[j + t], masks);
}
}
}
else if constexpr (range::count == 1)
{
const auto pos = static_cast<psnip_uint32_t>(range::pos_min);
for (; j + 8 <= nodes_in_interval; j += 8, k += 8)
{
alignas(node_type) node_type seeds[8];
alignas(node_type) node_type masks[8];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 8; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
DpfKey::exterior_prg::eval_x8(seeds, masks, pos);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 8; ++t)
{
apply_masks(k + t, nodes[j + t], &masks[t]);
}
}
for (; j + 4 <= nodes_in_interval; j += 4, k += 4)
{
alignas(node_type) node_type seeds[4];
alignas(node_type) node_type masks[4];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
DpfKey::exterior_prg::eval_x4(seeds, masks, pos);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
apply_masks(k + t, nodes[j + t], &masks[t]);
}
}
}
DPF_UNROLL_LOOP
for (; j < nodes_in_interval; ++j, ++k)
{
const auto & node = nodes[j];
auto seed = utils::to_exterior_node<node_type>(unset_lo_2bits(node));
std::array<node_type, range::count> masks;
DpfKey::exterior_prg::eval(seed, masks.data(),
static_cast<psnip_uint32_t>(range::count),
static_cast<psnip_uint32_t>(range::pos_min));
apply_masks(k, node, masks.data());
}
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <typename DpfKey,
typename InputT,
typename IntervalMemoizer>
void eval_prepare_nodes(const DpfKey & dpf, InputT from, InputT to,
IntervalMemoizer && memoizer)
{
using dpf_type = DpfKey;
using integral_type = typename DpfKey::integral_type;
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
integral_type from_node = utils::get_from_node<dpf_type>(from);
integral_type to_node = utils::get_to_node<dpf_type>(to);
auto segs = utils::split_leaf_nodes(from_node, to_node, dpf.depth);
// The memoizer keeps one interval. A wrap is two intervals, and walking
// the first clobbers the second, so only a single segment can be cached.
if (segs.n == 1)
{
eval_interval_interior(dpf, segs.seg[0].from_node, segs.seg[0].to_node,
memoizer);
}
}
template <std::size_t ...Is,
typename DpfKey,
typename InputT,
typename Weights,
typename IntervalMemoizer,
std::size_t ...IIs>
auto eval_inner_product_impl(const DpfKey & dpf, InputT from, InputT to,
Weights && weights, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>)
{
using dpf_type = DpfKey;
using integral_type = typename DpfKey::integral_type;
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
integral_type from_node = utils::get_from_node<dpf_type>(from);
integral_type to_node = utils::get_to_node<dpf_type>(to);
auto segs = utils::split_leaf_nodes(from_node, to_node, dpf.depth);
auto accs = std::make_tuple(
ip_accum<typename DpfKey::concrete_output_type<Is>>{}...);
auto idxs = std::index_sequence<IIs...>{};
std::size_t start = 0;
for (std::size_t s = 0; s < segs.n; ++s)
{
const auto & seg = segs.seg[s];
eval_interval_interior(dpf, seg.from_node, seg.to_node, memoizer);
eval_inner_product_exterior<Is...>(dpf, seg.from_node, seg.to_node,
weights, memoizer, idxs, accs, start);
start += seg.count;
}
if constexpr (sizeof...(Is) == 1)
{
return std::get<0>(accs).finish();
}
else
{
return std::make_tuple(std::get<IIs>(accs).finish()...);
}
}
} // namespace internal
/// Expand the interior tree for `[from, to]`. A wrapping interval is left
/// cold: the memoizer holds one half, and walking the first half of the later
/// inner product would clobber a cached second half. Safe to call before the
/// weight vector exists; a subsequent inner-product on the same memoizer
/// skips the interior AES when the interval did not wrap.
template <typename DpfKey,
typename InputT,
typename IntervalMemoizer>
HEDLEY_ALWAYS_INLINE
void eval_prepare_interval(const DpfKey & dpf, InputT from, InputT to,
IntervalMemoizer && memoizer)
{
internal::eval_prepare_nodes(dpf, dpf.offset_x(from), dpf.offset_x(to),
memoizer);
}
template <typename DpfKey,
typename IntervalMemoizer>
HEDLEY_ALWAYS_INLINE
void eval_prepare_full(const DpfKey & dpf, IntervalMemoizer && memoizer)
{
using input_type = typename DpfKey::input_type;
eval_prepare_interval(dpf,
std::numeric_limits<input_type>::min(),
std::numeric_limits<input_type>::max(),
memoizer);
}
/// `sum_x DPF_I(x) * w[x]` (additive) or `xor_x DPF_I(x) & w[x]` (XOR).
/// `w[j]` is the weight for the `j`-th output in the interval, matching
/// `eval_interval`'s destination layout. Multiple `Is` take a tuple of
/// weight ranges and return a tuple of accumulators; a single `I` takes
/// one range and returns one accumulator.
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename Weights,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_inner_product(const DpfKey & dpf, InputT from, InputT to,
Weights && weights, IntervalMemoizer && memoizer)
{
assert_not_wildcard_output<I, Is...>(dpf);
return internal::eval_inner_product_impl<I, Is...>(
dpf, dpf.offset_x(from), dpf.offset_x(to),
weights, memoizer, std::make_index_sequence<1 + sizeof...(Is)>{});
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename Weights,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_full_inner_product(const DpfKey & dpf, Weights && weights,
IntervalMemoizer && memoizer)
{
using input_type = typename DpfKey::input_type;
return eval_inner_product<I, Is...>(dpf,
std::numeric_limits<input_type>::min(),
std::numeric_limits<input_type>::max(),
weights, memoizer);
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_INNER_PRODUCT_HPP__

View file

@ -0,0 +1,462 @@
/// @file dpf/eval_interval.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @author Christopher Jiang <christopher.jiang@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__
#include <portable-snippets/builtin/builtin.h>
#include <portable-snippets/exact-int/exact-int.h>
#include "hedley/hedley.h"
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <array>
#include <tuple>
#include <type_traits>
#include <iterator>
#include <utility>
#include "dpf/dpf_key.hpp"
#include "dpf/eval_common.hpp"
#include "dpf/eval_target.hpp"
#include "dpf/output_buffer.hpp"
#include "dpf/interval_memoizer.hpp"
#include "dpf/subinterval_iterable.hpp"
namespace dpf
{
namespace internal
{
template <typename DpfKey,
typename IntervalMemoizer,
typename IntegralT = typename DpfKey::integral_type>
inline auto eval_interval_interior(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, IntervalMemoizer & memoizer, // NOLINT(runtime/references)
std::size_t to_level = DpfKey::depth)
{
using dpf_type = DpfKey;
using integral_type = typename DpfKey::integral_type;
using node_type = typename DpfKey::interior_node;
// level_index represents the current level being built
// level_index = 0 => root
// level_index = depth => last layer of interior nodes
std::size_t level_index = memoizer.assign_interval(dpf, from_node, to_node);
std::size_t nodes_at_level = memoizer.get_nodes_at_level();
integral_type mask = utils::get_node_mask<dpf_type>(dpf.msb_mask, level_index);
for (; level_index <= to_level; level_index = memoizer.advance_level(), nodes_at_level = memoizer.get_nodes_at_level(), mask>>=1)
{
std::size_t i = 0, j = 0;
bool from_offset = mask & from_node,
to_offset = from_offset ^ (nodes_at_level & 1);
const node_type cw[2] = {
dpf.correction_word(level_index-1, 0),
dpf.correction_word(level_index-1, 1)
};
auto *prev = memoizer[level_index-1];
auto *curr = memoizer[level_index];
// process node which only requires a right traversal
if (from_offset == true)
{
curr[i++] = dpf_type::traverse_interior(prev[j++], cw[1], 1);
}
// process all nodes which require both a left traversal and a right traversal
const std::size_t both_end = nodes_at_level - to_offset;
while (i + 8 <= both_end)
{
alignas(node_type) node_type parents[4];
alignas(node_type) node_type left[4];
alignas(node_type) node_type right[4];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
parents[t] = prev[j + t];
}
dpf_type::traverse_interior01_x4(parents, cw[0], cw[1], left, right);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
curr[i + 2 * t] = left[t];
curr[i + 2 * t + 1] = right[t];
}
i += 8;
j += 4;
}
DPF_UNROLL_LOOP
for (; i < both_end;)
{
auto cur_node = prev[j++];
auto kids = dpf_type::traverse_interior01(cur_node, cw[0], cw[1]);
curr[i++] = kids[0];
curr[i++] = kids[1];
}
// process node which only requires a left traversal
if (to_offset == true)
{
curr[i] = dpf_type::traverse_interior(prev[j], cw[0], 0);
}
}
}
template <std::size_t I,
typename DpfKey,
typename OutputBuffer,
typename IntervalMemoizer,
typename IntegralT = typename DpfKey::integral_type>
inline auto eval_interval_exterior(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, OutputBuffer && outbuf, IntervalMemoizer && memoizer,
std::size_t start = 0)
{
assert_not_wildcard_output<I>(dpf);
if (HEDLEY_UNLIKELY(to_node < from_node && to_node != IntegralT{0}))
throw std::runtime_error("to_node<from_node");
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
std::size_t nodes_in_interval = static_cast<std::size_t>(to_node - from_node);
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto cw = std::get<I>(dpf.leaf_nodes).get();
auto *nodes = memoizer[dpf_type::depth];
DPF_UNROLL_LOOP
for (std::size_t j = 0, k = start; j < nodes_in_interval; ++j, ++k)
{
auto leaf = dpf.template traverse_exterior<I>(nodes[j],
get_if_lo_bit(cw, nodes[j]));
if constexpr (utils::is_packed_subbyte_v<output_type>)
{
store_leaf_bytes(outbuf, k, leaf);
}
else
{
std::memcpy(&outbuf[k*dpf_type::outputs_per_leaf], &leaf,
sizeof(output_type) * dpf_type::outputs_per_leaf);
}
}
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <std::size_t I,
typename DpfKey,
typename OutputBuffer,
typename LeafT>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
void store_interval_leaf(OutputBuffer && outbuf, std::size_t k, const LeafT & leaf) noexcept
{
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
if constexpr (utils::is_packed_subbyte_v<output_type>)
{
store_leaf_bytes(outbuf, k, leaf);
}
else
{
std::memcpy(&outbuf[k * dpf_type::outputs_per_leaf], &leaf,
sizeof(output_type) * dpf_type::outputs_per_leaf);
}
}
/// One pass over the leaf-level interior nodes. When the selected output
/// indices occupy a contiguous PRG-position range, a single batched
/// `ExteriorPRG::eval` produces every output's leaf mask.
template <std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename IntervalMemoizer,
typename IntegralT,
std::size_t ...IIs>
inline void eval_interval_exterior_fused(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, OutputBuffers && outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>, std::size_t start = 0)
{
assert_not_wildcard_output<Is...>(dpf);
if (HEDLEY_UNLIKELY(to_node < from_node && to_node != IntegralT{0}))
throw std::runtime_error("to_node<from_node");
using node_type = typename DpfKey::exterior_node;
using outputs_tuple = typename DpfKey::concrete_outputs_tuple;
using range = leaf_prg_range<node_type, outputs_tuple, Is...>;
std::size_t nodes_in_interval = static_cast<std::size_t>(to_node - from_node);
auto *nodes = memoizer[DpfKey::depth];
auto cws = std::make_tuple(std::get<Is>(dpf.leaf_nodes).get()...);
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto apply_masks = [&](std::size_t k, const node_type & node,
const node_type * HEDLEY_RESTRICT masks)
{
auto apply_output = [&](auto out_index, auto buf_index)
{
constexpr std::size_t out_i = decltype(out_index)::value;
constexpr std::size_t buf_i = decltype(buf_index)::value;
using output_type = typename DpfKey::concrete_output_type<out_i>;
using leaf_type = dpf::leaf_node_t<node_type, output_type>;
constexpr auto pos = block_offset_of_leaf_v<out_i, node_type, outputs_tuple>;
leaf_type mask;
std::memcpy(&mask, masks + (pos - range::pos_min), sizeof(leaf_type));
// Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β.
auto leaf = dpf::subtract_leaf<output_type>(
get_if_lo_bit(std::get<buf_i>(cws), node), mask);
store_interval_leaf<out_i, DpfKey>(utils::get<buf_i>(outbufs), k, leaf);
};
(apply_output(std::integral_constant<std::size_t, Is>{},
std::integral_constant<std::size_t, IIs>{}), ...);
};
std::size_t j = 0, k = start;
if constexpr (range::count == 2 && range::pos_min == 0)
{
for (; j + 4 <= nodes_in_interval; j += 4, k += 4)
{
alignas(node_type) node_type seeds[4];
alignas(node_type) node_type left[4];
alignas(node_type) node_type right[4];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
DpfKey::exterior_prg::eval01_x4(seeds, left, right);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
node_type masks[2] = {left[t], right[t]};
apply_masks(k + t, nodes[j + t], masks);
}
}
}
else if constexpr (range::count == 1)
{
const auto pos = static_cast<psnip_uint32_t>(range::pos_min);
for (; j + 8 <= nodes_in_interval; j += 8, k += 8)
{
alignas(node_type) node_type seeds[8];
alignas(node_type) node_type masks[8];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 8; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
DpfKey::exterior_prg::eval_x8(seeds, masks, pos);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 8; ++t)
{
apply_masks(k + t, nodes[j + t], &masks[t]);
}
}
for (; j + 4 <= nodes_in_interval; j += 4, k += 4)
{
alignas(node_type) node_type seeds[4];
alignas(node_type) node_type masks[4];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
DpfKey::exterior_prg::eval_x4(seeds, masks, pos);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
apply_masks(k + t, nodes[j + t], &masks[t]);
}
}
}
DPF_UNROLL_LOOP
for (; j < nodes_in_interval; ++j, ++k)
{
const auto & node = nodes[j];
auto seed = utils::to_exterior_node<node_type>(unset_lo_2bits(node));
std::array<node_type, range::count> masks;
DpfKey::exterior_prg::eval(seed, masks.data(),
static_cast<psnip_uint32_t>(range::count),
static_cast<psnip_uint32_t>(range::pos_min));
apply_masks(k, node, masks.data());
}
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename IntervalMemoizer,
typename IntegralT,
std::size_t ...IIs>
HEDLEY_ALWAYS_INLINE
void eval_interval_exterior_all(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, OutputBuffers && outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...> idxs, std::size_t start = 0)
{
using node_type = typename DpfKey::exterior_node;
using outputs_tuple = typename DpfKey::concrete_outputs_tuple;
using range = leaf_prg_range<node_type, outputs_tuple, Is...>;
if constexpr (range::is_contiguous)
{
eval_interval_exterior_fused<Is...>(dpf, from_node, to_node, outbufs,
memoizer, idxs, start);
}
else
{
(eval_interval_exterior<Is>(dpf, from_node, to_node,
utils::get<IIs>(outbufs), memoizer, start), ...);
}
}
template <std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer,
std::size_t ...IIs>
auto eval_interval_impl(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers && outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>)
{
using dpf_type = DpfKey;
using integral_type = typename DpfKey::integral_type;
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
integral_type from_node = utils::get_from_node<dpf_type>(from),
to_node = utils::get_to_node<dpf_type>(to);
auto segs = utils::split_leaf_nodes(from_node, to_node, dpf.depth);
auto idxs = std::index_sequence<IIs...>{};
std::size_t start = 0;
for (std::size_t s = 0; s < segs.n; ++s)
{
const auto & seg = segs.seg[s];
internal::eval_interval_interior(dpf, seg.from_node, seg.to_node, memoizer);
eval_interval_exterior_all<Is...>(dpf, seg.from_node, seg.to_node, outbufs,
memoizer, idxs, start);
start += seg.count;
}
}
template <std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer,
std::size_t ...IIs>
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers && outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>)
{
using dpf_type = DpfKey;
constexpr auto mod_pow_2 = utils::mod_pow_2<InputT>{};
constexpr auto to_integral_t = utils::to_integral_type<InputT>{};
constexpr auto bits = utils::bitlength_of_v<InputT>;
eval_interval_impl<Is...>(dpf, from, to, outbufs, memoizer, std::make_index_sequence<sizeof...(Is)>());
// `to_integral_type` widens to at least `size_t`. Subtracting in that
// wider type loses wrap-around of a narrower input domain (e.g. int16
// intervals that increment across 0). Mask back to the domain width so
// `subinterval_iterable` length matches the inclusive [from, to] walk.
auto from_i = to_integral_t(from);
auto span = to_integral_t(to) - from_i;
if constexpr (bits < utils::bitlength_of_v<decltype(span)>)
{
span &= (decltype(span){1} << bits) - 1;
}
auto from_sz = static_cast<std::size_t>(from_i);
auto to_sz = from_sz + static_cast<std::size_t>(span);
return utils::make_tuple(subinterval_iterable(std::begin(utils::get<IIs>(outbufs)), utils::size(utils::get<IIs>(outbufs)), from_sz, to_sz, mod_pow_2(from, dpf_type::lg_outputs_per_leaf), dpf_type::outputs_per_leaf)...);
}
} // namespace internal
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer = dpf::basic_interval_memoizer<DpfKey>,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs, IntervalMemoizer && memoizer) // NOLINT(runtime/references)
{
assert_not_wildcard_output<I, Is...>(dpf);
return internal::eval_interval<I, Is...>(dpf, dpf.offset_x(from), dpf.offset_x(to), outbufs, memoizer, std::make_index_sequence<1+sizeof...(Is)>());
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<!std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<OutputBuffers>>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs) // NOLINT(runtime/references)
{
return eval_interval<I, Is...>(dpf, from, to, outbufs,
dpf::make_basic_interval_memoizer<DpfKey>(from, to));
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<IntervalMemoizer>>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
IntervalMemoizer && memoizer)
{
auto outbufs = utils::make_tuple(
make_output_buffer_for_interval<I>(dpf, from, to),
make_output_buffer_for_interval<Is>(dpf, from, to)...);
// moving `outbufs` is allowed as the `outbufs` are `std::vectors`
// the underlying data remains on the heap
// and thus the data the iterable refers to is still valid
auto iterable = eval_interval<I, Is...>(dpf, from, to, outbufs, memoizer);
return std::make_pair(std::move(outbufs), std::move(iterable));
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to)
{
return eval_interval<I, Is...>(dpf, from, to,
dpf::make_basic_interval_memoizer<DpfKey>(from, to));
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__

109
include/dpf/eval_point.hpp Normal file
View file

@ -0,0 +1,109 @@
/// @file dpf/eval_point.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @author Christopher Jiang <christopher.jiang@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_EVAL_POINT_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_POINT_HPP__
#include <portable-snippets/builtin/builtin.h>
#include "hedley/hedley.h"
#include <cstddef>
#include <tuple>
#include "dpf/dpf_key.hpp"
#include "dpf/eval_common.hpp"
#include "dpf/eval_target.hpp"
#include "dpf/path_memoizer.hpp"
namespace dpf
{
namespace internal
{
template <typename DpfKey,
typename InputT,
typename PathMemoizer>
inline auto eval_point_interior(const DpfKey & dpf, InputT && x, PathMemoizer && path)
{
using dpf_type = DpfKey;
auto level_index = detail::path_resume_for_level(path, dpf, x, dpf.depth);
DPF_UNROLL_LOOP
for (auto mask = dpf.msb_mask>>(level_index-1);
level_index <= dpf.depth; ++level_index, mask>>=1)
{
bool bit = !!(mask & x);
auto cw = dpf.correction_word(level_index-1, bit);
path[level_index] = dpf_type::traverse_interior(path[level_index-1], cw, bit);
}
detail::path_note_filled_to(path, dpf.depth);
}
template <std::size_t I,
typename DpfKey,
typename PathMemoizer>
inline auto eval_point_exterior(const DpfKey & dpf, PathMemoizer && path)
{
assert_not_wildcard_output<I>(dpf);
auto interior = path[dpf.depth];
return dpf.template traverse_exterior<I>(interior);
}
template <std::size_t I,
typename DpfKey,
typename InputT,
typename PathMemoizer>
HEDLEY_ALWAYS_INLINE
auto eval_point(const DpfKey & dpf, InputT && x, PathMemoizer && path)
{
utils::flip_msb_if_signed_integral(x);
internal::eval_point_interior(dpf, x, path);
return internal::eval_point_exterior<I>(dpf, path);
}
} // namespace internal
template <std::size_t I = 0,
typename DpfKey,
typename InputT,
typename PathMemoizer = dpf::nonmemoizing_path_memoizer<DpfKey>,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_point(const DpfKey & dpf, InputT && x, PathMemoizer && path = PathMemoizer{})
{
assert_not_wildcard_output<I>(dpf);
using output_type = typename DpfKey::concrete_output_type<I>;
auto tx = dpf.offset_x(x);
return make_eval_dpf_output<DpfKey, output_type>(
internal::eval_point<I>(dpf, tx, path), tx);
}
template <std::size_t I0,
std::size_t I1,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename PathMemoizer = dpf::basic_path_memoizer<DpfKey>,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_point(const DpfKey & dpf, InputT && x, PathMemoizer && path = PathMemoizer{})
{
return std::make_tuple(
*eval_point<I0>(dpf, x, path),
*eval_point<I1>(dpf, x, path),
*eval_point<Is>(dpf, x, path)...);
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_POINT_HPP__

View file

@ -0,0 +1,522 @@
/// @file dpf/eval_sequence.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @author Christopher Jiang <christopher.jiang@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_EVAL_SEQUENCE_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_SEQUENCE_HPP__
#include <portable-snippets/builtin/builtin.h>
#include "hedley/hedley.h"
#include <cstddef>
#include <cstring>
#include <type_traits>
#include <utility>
#include <tuple>
#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <list>
#include "dpf/dpf_key.hpp"
#include "dpf/eval_common.hpp"
#include "dpf/eval_target.hpp"
#include "dpf/eval_point.hpp"
#include "dpf/path_memoizer.hpp"
#include "dpf/sequence_memoizer.hpp"
#include "dpf/sequence_utils.hpp"
#include "dpf/subsequence_iterable.hpp"
#include "dpf/subinterval_iterable.hpp"
namespace dpf
{
namespace internal
{
template <std::size_t ...Is,
typename DpfKey,
typename ForwardIterator,
typename OutputBuffers,
std::size_t ...IIs>
auto eval_sequence_entire_node(const DpfKey & dpf, ForwardIterator begin, ForwardIterator end,
OutputBuffers && outbufs, std::index_sequence<IIs...>)
{
static constexpr std::size_t outputs_per_leaf = DpfKey::outputs_per_leaf;
auto path = make_basic_path_memoizer(dpf);
std::size_t i = 0;
// DPF_UNROLL_LOOP
for (auto it = begin; it != end; ++it, ++i)
{
if constexpr(utils::is_packed_subbyte_v<typename DpfKey::concrete_output_type<0>>)
{
auto nodes = std::make_tuple(dpf::eval_point<Is>(dpf, *it, path).node...);
(store_leaf_bytes(utils::get<IIs>(outbufs), i, std::get<IIs>(nodes)), ...);
}
else
{
auto temp = std::make_tuple(dpf::eval_point<Is>(dpf, *it, path).node...);
(std::memcpy(&utils::get<IIs>(outbufs)[i*outputs_per_leaf], &utils::get<IIs>(temp), sizeof(typename DpfKey::concrete_output_type<Is>)*outputs_per_leaf), ...);
}
}
return utils::make_tuple(
dpf::subsequence_iterable<DpfKey, decltype(std::begin(utils::get<IIs>(outbufs))), ForwardIterator>(std::begin(utils::get<IIs>(outbufs)), begin, end)...);
}
template <typename Slot, typename Val>
HEDLEY_ALWAYS_INLINE
void assign_eval_slot(Slot && slot, Val && val)
{
using val_t = std::decay_t<Val>;
if constexpr (is_secret_share_v<std::decay_t<Slot>>)
{
using elem_t = std::decay_t<Slot>;
if constexpr (is_secret_share_v<val_t>)
slot = elem_t::from_raw(val.raw());
else
slot = elem_t::from_raw(static_cast<typename elem_t::value_type>(val));
}
else if constexpr (is_secret_share_v<val_t>)
{
slot = val.raw();
}
else
{
slot = std::forward<Val>(val);
}
}
template <std::size_t ...Is,
typename DpfKey,
typename ForwardIterator,
typename OutputBuffers,
std::size_t ...IIs>
auto eval_sequence_output_only(const DpfKey & dpf, ForwardIterator begin, ForwardIterator end, OutputBuffers && outbufs,
std::index_sequence<IIs...>)
{
auto path = make_basic_path_memoizer(dpf);
std::size_t i = 0;
// DPF_UNROLL_LOOP
for (auto it = begin; it != end; ++it, ++i)
{
(assign_eval_slot(utils::get<IIs>(outbufs)[i],
*dpf::eval_point<Is>(dpf, *it, path)), ...);
}
if (i == 0)
{
return utils::make_tuple(subinterval_iterable(std::begin(utils::get<IIs>(outbufs)), utils::size(utils::get<IIs>(outbufs)), 0, 0, 0, 0, false)...);
}
return utils::make_tuple(subinterval_iterable(std::begin(utils::get<IIs>(outbufs)), utils::size(utils::get<IIs>(outbufs)), 0, i-1, 0, 0)...);
}
} // namespace internal
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename ForwardIterator,
typename OutputBuffers,
typename ReturnType = return_entire_node_tag_,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<std::is_base_of_v<return_type_tag_, ReturnType>, bool> = true,
std::enable_if_t<!std::is_base_of_v<return_type_tag_, OutputBuffers>, bool> = true>
inline auto eval_sequence(const DpfKey & dpf, ForwardIterator begin, ForwardIterator end,
OutputBuffers && outbufs, ReturnType return_type = ReturnType{})
{
static_assert(std::is_same_v<ReturnType, return_entire_node_tag_> ||
std::is_same_v<ReturnType, return_output_only_tag_>);
if constexpr(std::is_same_v<ReturnType, return_entire_node_tag_>)
{
return internal::eval_sequence_entire_node<I, Is...>(dpf, begin, end, outbufs, std::make_index_sequence<1+sizeof...(Is)>{});
}
else
{
return internal::eval_sequence_output_only<I, Is...>(dpf, begin, end, outbufs, std::make_index_sequence<1+sizeof...(Is)>{});
}
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename ForwardIterator,
typename ReturnType = return_entire_node_tag_,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<std::is_base_of_v<return_type_tag_, ReturnType>, bool> = true>
auto eval_sequence(const DpfKey & dpf, ForwardIterator begin, ForwardIterator end,
ReturnType return_type = ReturnType{})
{
auto outbufs = utils::make_tuple(
make_output_buffer_for_subsequence<I>(dpf, begin, end, return_type),
make_output_buffer_for_subsequence<Is>(dpf, begin, end, return_type)...);
// moving `outbufs` is allowed as the `outbufs` are `std::vectors`
// the underlying data remains on the heap
// and thus the data the iterable refers to is still valid
auto iterable = eval_sequence<I, Is...>(dpf, begin, end, outbufs, return_type);
return std::make_pair(std::move(outbufs), std::move(iterable));
}
template <std::size_t I = 0,
typename DpfKey,
typename ForwardIterator,
typename OutputBuffer>
inline auto eval_sequence_breadth_first(const DpfKey & dpf, ForwardIterator begin, ForwardIterator end, OutputBuffer && outbuf)
{
assert_not_wildcard_output<I>(dpf);
using dpf_type = DpfKey;
using input_type = typename DpfKey::input_type;
using node_type = typename DpfKey::interior_node;
using output_type = typename DpfKey::concrete_output_type<I>;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using allocator = aligned_allocator<typename DpfKey::interior_node>;
using unique_ptr = typename allocator::unique_ptr;
HEDLEY_PRAGMA(GCC diagnostic pop)
allocator alloc = allocator{};
if (HEDLEY_UNLIKELY(!std::is_sorted(begin, end)))
{
throw std::runtime_error("list must be sorted");
}
if (begin == end)
{
return subsequence_iterable<DpfKey, decltype(std::begin(outbuf)), ForwardIterator>(
std::begin(outbuf), begin, end);
}
auto mask = dpf_type::msb_mask;
std::size_t nodes_in_sequence = std::distance(begin, end);
unique_ptr memo{alloc.allocate_unique_ptr(nodes_in_sequence*2)};
bool curhalf = (dpf_type::depth ^ 1) & 1;
memo[!curhalf*nodes_in_sequence + 0] = dpf.root();
std::list<ForwardIterator> splits{begin, end};
std::size_t level_index = 1;
auto func = [&](const bool flip = false)
{
std::size_t i = 0, j = 0;
const node_type cw[2] = {
dpf.correction_word(level_index-1, 0),
dpf.correction_word(level_index-1, 1)
};
// `lower` and `upper` are always adjacent elements of `splits` with `lower` < `upper`
// [lower, upper) = "block"
for (auto upper = std::begin(splits), lower = upper++; upper != std::end(splits); lower = upper++)
{
// `upper_bound()` returns iterator to first element where the relevant bit (based on `mask`) is set
auto it = std::upper_bound(*lower, *upper, mask,
[&flip](auto a, auto b){ return static_cast<bool>(a&b) ^ flip; });
if (it == *lower) // right only since first element in "block" requires right traversal
{
memo[curhalf*nodes_in_sequence + i++] = dpf_type::traverse_interior(memo[!curhalf*nodes_in_sequence + j++], cw[1], 1);
}
else if (it == *upper) // left only since no element in "block" requires right traversal
{
memo[curhalf*nodes_in_sequence + i++] = dpf_type::traverse_interior(memo[!curhalf*nodes_in_sequence + j++], cw[0], 0);
}
else // both ways since some (non-lower) element within "block" requires right traversal
{
auto cur_node = memo[!curhalf*nodes_in_sequence + j++];
auto kids = dpf_type::traverse_interior01(cur_node, cw[0], cw[1]);
memo[curhalf*nodes_in_sequence + i++] = kids[0];
memo[curhalf*nodes_in_sequence + i++] = kids[1];
splits.insert(upper, it);
}
}
};
if (dpf_type::depth >= level_index)
{
func(utils::uses_signed_msb_v<input_type>);
++level_index;
mask >>= 1;
curhalf =! curhalf;
}
for (; level_index <= dpf_type::depth; ++level_index, mask>>=1, curhalf=!curhalf)
{
func();
}
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto cw = dpf.template leaf<I>();
auto buf = memo.get();
constexpr auto clz = utils::countl_zero_symmetric_difference<input_type>{};
auto curr = begin, prev = curr;
for (std::size_t i = 0, j = 0; i < nodes_in_sequence; ++i)
{
j += (clz(*prev, *curr)) < dpf_type::depth;
auto leaf = dpf_type::template traverse_exterior<I>(buf[j],
get_if_lo_bit(cw, buf[j]));
if constexpr (utils::is_packed_subbyte_v<output_type>)
{
store_leaf_bytes(outbuf, i, leaf);
}
else
{
std::memcpy(&outbuf[i*dpf_type::outputs_per_leaf], &leaf, sizeof(output_type)*dpf_type::outputs_per_leaf);
}
prev = curr++;
}
HEDLEY_PRAGMA(GCC diagnostic pop)
return subsequence_iterable<DpfKey, decltype(std::begin(outbuf)), ForwardIterator>(std::begin(outbuf), begin, end);
}
template <std::size_t I = 0,
typename DpfKey,
typename ForwardIterator>
auto eval_sequence_breadth_first(const DpfKey & dpf, ForwardIterator begin, ForwardIterator end)
{
auto outbuf = make_output_buffer_for_subsequence<I>(dpf, begin, end);
// moving `outbuf` is allowed as `outbuf` is a `std::vectors`
// the underlying data remains on the heap
// and thus the data the iterable refers to is still valid
auto iterable = eval_sequence_breadth_first<I>(dpf, begin, end, outbuf);
return std::make_pair(std::move(outbuf), std::move(iterable));
}
namespace internal
{
template <typename DpfKey,
typename SequenceMemoizer>
inline auto eval_sequence_interior(const DpfKey & dpf, const sequence_recipe & recipe,
SequenceMemoizer && memoizer, std::size_t to_level = DpfKey::depth)
{
using dpf_type = DpfKey;
using node_type = typename DpfKey::interior_node;
// level_index represents the current level being built
// level_index = 0 => root
// level_index = depth => last layer of interior nodes
if (recipe.num_leaf_nodes() == 0)
return;
std::size_t level_index = memoizer.assign_dpf(dpf, recipe);
std::size_t recipe_index = recipe.level_endpoints()[level_index-1];
std::size_t nodes_at_level = memoizer.get_nodes_at_level(level_index-1);
for (; level_index <= to_level; level_index = memoizer.advance_level(), nodes_at_level = memoizer.get_nodes_at_level(level_index-1))
{
const node_type cw[2] = {
dpf.correction_word(level_index-1, 0),
dpf.correction_word(level_index-1, 1)
};
auto prevbuf = memoizer[level_index-1];
auto currbuf = memoizer[level_index];
DPF_UNROLL_LOOP
for (std::size_t input_index = 0, output_index = 0; input_index < nodes_at_level; ++input_index, ++recipe_index)
{
if (memoizer.traverse_first(recipe_index) == true)
{
bool dir = memoizer.get_direction(0);
currbuf[output_index++] = dpf_type::traverse_interior(prevbuf[input_index], cw[dir], dir);
}
if (memoizer.traverse_second(recipe_index) == true)
{
bool dir = memoizer.get_direction(1);
currbuf[output_index++] = dpf_type::traverse_interior(prevbuf[input_index], cw[dir], dir);
}
}
}
}
template <std::size_t I,
typename DpfKey,
typename OutputBuffer,
typename SequenceMemoizer>
inline auto eval_sequence_exterior_entire_node(const DpfKey & dpf, const sequence_recipe & recipe,
OutputBuffer && outbuf, SequenceMemoizer && memoizer)
{
assert_not_wildcard_output<I>(dpf);
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
auto nodes_in_interval = recipe.num_leaf_nodes();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto buf = memoizer[dpf.depth];
DPF_UNROLL_LOOP
for (std::size_t j = 0; j < nodes_in_interval; ++j)
{
auto leaf = dpf.template traverse_exterior<I>(buf[j]);
if constexpr (utils::is_packed_subbyte_v<output_type>)
{
store_leaf_bytes(outbuf, j, leaf);
}
else
{
std::memcpy(&outbuf[j*dpf_type::outputs_per_leaf], &leaf, sizeof(output_type)*dpf_type::outputs_per_leaf);
}
}
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <std::size_t I,
typename DpfKey,
typename OutputBuffer,
typename SequenceMemoizer>
inline auto eval_sequence_exterior_output_only(const DpfKey & dpf, const sequence_recipe & recipe,
OutputBuffer && outbuf, SequenceMemoizer && memoizer)
{
assert_not_wildcard_output<I>(dpf);
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto cw = dpf.template leaf<I>();
using node_type = typename DpfKey::exterior_node;
using leaf_node_type = std::tuple_element_t<I, typename DpfKey::leaf_tuple>;
auto buf = memoizer[dpf.depth];
leaf_node_type node;
// DPF_UNROLL_LOOP
for (std::size_t i = 0, j = -1, prev = -1, curr;
i < recipe.output_indices().size();
prev = curr, ++i)
{
curr = recipe.output_indices()[i]/dpf_type::outputs_per_leaf;
if (prev != curr)
{
++j;
node = dpf_type::template traverse_exterior<I>(buf[j], get_if_lo_bit(cw, buf[j]));
}
auto v = extract_leaf<node_type, output_type>(node,
recipe.output_indices()[i] % dpf_type::outputs_per_leaf);
using elem_t = std::decay_t<decltype(outbuf[i])>;
if constexpr (is_secret_share_v<elem_t>)
outbuf[i] = elem_t::from_raw(v);
else
outbuf[i] = v;
}
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename SequenceMemoizer,
typename ReturnType = return_entire_node_tag_,
std::enable_if_t<std::is_base_of_v<return_type_tag_, ReturnType>, bool> = true,
std::size_t ...IIs>
auto eval_sequence(const DpfKey & dpf, const sequence_recipe & recipe,
OutputBuffers && outbufs, SequenceMemoizer && memoizer, ReturnType return_type, std::index_sequence<IIs...>)
{
internal::eval_sequence_interior(dpf, recipe, memoizer);
static_assert(std::is_same_v<ReturnType, return_entire_node_tag_> ||
std::is_same_v<ReturnType, return_output_only_tag_>);
if constexpr (std::is_same_v<ReturnType, return_entire_node_tag_>)
{
(internal::eval_sequence_exterior_entire_node<Is>(dpf, recipe, utils::get<IIs>(outbufs), memoizer), ...);
return utils::make_tuple(
recipe_subsequence_iterable(std::begin(utils::get<IIs>(outbufs)), recipe.output_indices())...);
}
else
{
(internal::eval_sequence_exterior_output_only<Is>(dpf, recipe, utils::get<IIs>(outbufs), memoizer), ...);
const auto nout = recipe.output_indices().size();
if (nout == 0)
{
return utils::make_tuple(subinterval_iterable(std::begin(utils::get<IIs>(outbufs)), utils::size(utils::get<IIs>(outbufs)), 0, 0, 0, 0, false)...);
}
return utils::make_tuple(subinterval_iterable(std::begin(utils::get<IIs>(outbufs)), utils::size(utils::get<IIs>(outbufs)), 0, nout-1, 0, 0)...);
}
}
} // namespace internal
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename SequenceMemoizer,
typename ReturnType = return_entire_node_tag_,
std::enable_if_t<!std::is_base_of_v<return_type_tag_, SequenceMemoizer>, bool> = true,
std::enable_if_t<std::is_base_of_v<return_type_tag_, ReturnType>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_sequence(const DpfKey & dpf, const sequence_recipe & recipe,
OutputBuffers & outbufs, SequenceMemoizer && memoizer, // NOLINT(runtime/references)
ReturnType return_type = ReturnType{})
{
assert_not_wildcard_output<I, Is...>(dpf);
assert_not_wildcard_input(dpf);
return internal::eval_sequence<I, Is...>(dpf, recipe, outbufs, memoizer, return_type, std::make_index_sequence<1+sizeof...(Is)>());
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename ReturnType = return_entire_node_tag_,
std::enable_if_t<!std::is_base_of_v<sequence_memoizer_tag_,
std::decay_t<OutputBuffers>>, bool> = true,
std::enable_if_t<std::is_base_of_v<return_type_tag_, ReturnType>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_sequence(const DpfKey & dpf, const sequence_recipe & recipe,
OutputBuffers & outbufs, ReturnType return_type = ReturnType{}) // NOLINT(runtime/references)
{
return eval_sequence<I, Is...>(dpf, recipe, outbufs,
dpf::make_double_space_sequence_memoizer<DpfKey>(recipe), return_type);
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename SequenceMemoizer,
typename ReturnType = return_entire_node_tag_,
std::enable_if_t<std::is_base_of_v<sequence_memoizer_tag_,
std::decay_t<SequenceMemoizer>>, bool> = true,
std::enable_if_t<std::is_base_of_v<return_type_tag_, ReturnType>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_sequence(const DpfKey & dpf, const sequence_recipe & recipe,
SequenceMemoizer && memoizer, ReturnType return_type = ReturnType{})
{
auto outbufs = utils::make_tuple(
make_output_buffer_for_recipe_subsequence<I>(dpf, recipe, return_type),
make_output_buffer_for_recipe_subsequence<Is>(dpf, recipe, return_type)...);
// moving `outbufs` is allowed as the `outbufs` are `std::vectors`
// the underlying data remains on the heap
// and thus the data the iterable refers to is still valid
auto iterable = eval_sequence<I, Is...>(dpf, recipe, outbufs, memoizer, return_type);
return std::make_pair(std::move(outbufs), std::move(iterable));
}
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename ReturnType = return_entire_node_tag_,
std::enable_if_t<std::is_base_of_v<return_type_tag_, ReturnType>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_sequence(const DpfKey & dpf, const sequence_recipe & recipe,
ReturnType return_type = ReturnType{})
{
return eval_sequence<I, Is...>(dpf, recipe,
dpf::make_double_space_sequence_memoizer<DpfKey>(recipe), return_type);
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_SEQUENCE_HPP__

110
include/dpf/eval_target.hpp Normal file
View file

@ -0,0 +1,110 @@
/// @file dpf/eval_target.hpp
/// @brief Eval channel tags for the unified DPF / iDPF / DCF surface.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
#ifndef LIBDPF_INCLUDE_DPF_EVAL_TARGET_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_TARGET_HPP__
#include <cstddef>
#include <limits>
#include <type_traits>
namespace dpf
{
/// Sentinel: deduce point-slot prefix from the key (`meta[I].prefix`).
inline constexpr std::size_t prefix_deduce =
std::numeric_limits<std::size_t>::max();
/// Point-output channel: slot `I`, optional prefix check `N`.
template <std::size_t I = 0, std::size_t N = prefix_deduce>
struct out_t
{
static constexpr std::size_t index = I;
static constexpr std::size_t prefix = N;
static constexpr bool prefix_fixed = (N != prefix_deduce);
};
template <std::size_t I = 0, std::size_t N = prefix_deduce>
inline constexpr out_t<I, N> out{};
/// Comparison (DCF) channel.
struct cmp_t
{
};
inline constexpr cmp_t cmp{};
template <typename T>
struct is_out : std::false_type
{
};
template <std::size_t I, std::size_t N>
struct is_out<out_t<I, N>> : std::true_type
{
};
template <typename T>
inline constexpr bool is_out_v = is_out<std::decay_t<T>>::value;
template <typename T>
struct is_cmp_target : std::bool_constant<std::is_same_v<std::decay_t<T>, cmp_t>>
{
};
template <typename T>
inline constexpr bool is_cmp_target_v = is_cmp_target<T>::value;
/// True for channel tags that must not bind as the key in classic eval_*.
template <typename T>
inline constexpr bool is_eval_channel_tag_v =
is_out_v<T> || is_cmp_target_v<T>;
template <typename T, typename = void>
struct looks_like_dpf_key : std::false_type
{
};
template <typename T>
struct looks_like_dpf_key<T,
std::void_t<typename T::input_type, typename T::interior_node>>
: std::true_type
{
};
template <typename T>
inline constexpr bool looks_like_dpf_key_v =
looks_like_dpf_key<std::decay_t<T>>::value;
template <typename T, typename = void>
struct is_incremental_dpf_key : std::false_type
{
};
template <typename T>
struct is_incremental_dpf_key<T,
std::void_t<decltype(T::cmp_depth), decltype(T::meta),
decltype(T::deepest_output)>> : std::true_type
{
};
template <typename T>
inline constexpr bool is_incremental_dpf_key_v =
is_incremental_dpf_key<std::decay_t<T>>::value;
/// True only for keys that must use the slot-aware (multi-level / comparison)
/// eval path. Every key now carries a `slot_meta` table (so
/// `is_incremental_dpf_key_v` is true for all keys), but classic single-level
/// equal-width keys keep using the classic `eval_*` fast paths; they set
/// `is_multilevel == false`. Multi-level (`at<N>`) and comparison keys set it
/// to true.
template <typename T, typename = void>
struct is_multilevel_key : std::false_type
{
};
template <typename T>
struct is_multilevel_key<T, std::void_t<decltype(T::is_multilevel)>>
: std::bool_constant<T::is_multilevel>
{
};
template <typename T>
inline constexpr bool is_multilevel_key_v =
is_multilevel_key<std::decay_t<T>>::value;
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_TARGET_HPP__

View file

@ -0,0 +1,642 @@
/// @file dpf/eval_unified.hpp
/// @brief Target-first eval surface for DPF / iDPF / DCF channels.
/// @details `eval_*(out<I>, …)` and `eval_*(cmp, …)` are the public API.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
#ifndef LIBDPF_INCLUDE_DPF_EVAL_UNIFIED_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_UNIFIED_HPP__
#include "hedley/hedley.h"
#include <cstddef>
#include <cstring>
#include <algorithm>
#include <iterator>
#include <list>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <portable-snippets/exact-int/exact-int.h>
#include "dpf/eval_target.hpp"
#include "dpf/eval_point.hpp"
#include "dpf/eval_interval.hpp"
#include "dpf/eval_full.hpp"
#include "dpf/eval_sequence.hpp"
#include "dpf/sequence_recipe.hpp"
#include "dpf/incremental.hpp"
#include "dpf/path_memoizer.hpp"
#include "dpf/interval_memoizer.hpp"
#include "dpf/aligned_allocator.hpp"
#include "dpf/leaf_node.hpp"
namespace dpf
{
namespace detail
{
template <std::size_t I, std::size_t N, typename KeyT>
constexpr std::size_t resolved_out_prefix() noexcept
{
if constexpr (is_multilevel_key_v<KeyT>)
{
if constexpr (N != prefix_deduce)
{
static_assert(KeyT::meta[I].prefix == N,
"out<I,N>: N does not match key::meta[I].prefix");
return N;
}
else
return KeyT::meta[I].prefix;
}
else
{
(void)N;
return utils::bitlength_of_v<typename KeyT::input_type>;
}
}
} // namespace detail
// ---------------------------------------------------------------------------
// eval_point(target, key, x [, path])
// ---------------------------------------------------------------------------
template <std::size_t I, std::size_t N, typename KeyT, typename QueryT,
typename PathMemoizer = nonmemoizing_path_memoizer<KeyT>>
auto eval_point(out_t<I, N>, const KeyT & key, QueryT && x,
PathMemoizer && path = PathMemoizer{})
{
if constexpr (is_multilevel_key_v<KeyT>)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_point_impl<pref, I>(key,
std::forward<QueryT>(x), std::forward<PathMemoizer>(path));
}
else
{
return eval_point<I>(key, std::forward<QueryT>(x),
std::forward<PathMemoizer>(path));
}
}
template <typename Beta = uint64_t, typename KeyT, typename QueryT,
typename PathMemoizer = basic_path_memoizer<KeyT>>
auto eval_point(cmp_t, const KeyT & key, QueryT && x,
PathMemoizer && path = PathMemoizer{})
{
return detail::incr::eval_cmp_point_impl<Beta>(key, std::forward<QueryT>(x),
std::forward<PathMemoizer>(path));
}
// ---------------------------------------------------------------------------
// eval_interval(target, key, from, to [, buf [, memo]])
// ---------------------------------------------------------------------------
template <std::size_t I, std::size_t N, typename KeyT, typename LaneT,
typename OutputBuffer, typename IntervalMemoizer>
auto eval_interval(out_t<I, N>, const KeyT & key, LaneT from, LaneT to,
OutputBuffer && outbuf, IntervalMemoizer && memo)
{
if constexpr (is_multilevel_key_v<KeyT>)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_interval_impl<pref, I>(key, from, to,
std::forward<OutputBuffer>(outbuf),
std::forward<IntervalMemoizer>(memo));
}
else
{
return eval_interval<I>(key, from, to,
std::forward<OutputBuffer>(outbuf),
std::forward<IntervalMemoizer>(memo));
}
}
template <std::size_t I, std::size_t N, typename KeyT, typename LaneT,
typename OutputBuffer>
auto eval_interval(out_t<I, N>, const KeyT & key, LaneT from, LaneT to,
OutputBuffer && outbuf)
{
if constexpr (is_multilevel_key_v<KeyT>)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_interval_impl<pref, I>(key, from, to,
std::forward<OutputBuffer>(outbuf));
}
else
{
return eval_interval<I>(key, from, to,
std::forward<OutputBuffer>(outbuf));
}
}
template <std::size_t I, std::size_t N, typename KeyT, typename LaneT>
auto eval_interval(out_t<I, N>, const KeyT & key, LaneT from, LaneT to)
{
if constexpr (is_multilevel_key_v<KeyT>)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_interval_impl<pref, I>(key, from, to);
}
else
{
return eval_interval<I>(key, from, to);
}
}
template <typename Beta = uint64_t, typename KeyT, typename LaneT,
typename OutputBuffer>
void eval_interval(cmp_t, const KeyT & key, LaneT from, LaneT to,
OutputBuffer && outbuf)
{
detail::incr::eval_cmp_interval_impl<Beta>(key, from, to,
std::forward<OutputBuffer>(outbuf));
}
template <typename Beta = uint64_t, typename KeyT, typename LaneT,
typename OutputBuffer, typename IntervalMemoizer>
void eval_interval(cmp_t, const KeyT & key, LaneT from, LaneT to,
OutputBuffer && outbuf, IntervalMemoizer && memo)
{
detail::incr::eval_cmp_interval_impl<Beta>(key, from, to,
std::forward<OutputBuffer>(outbuf),
std::forward<IntervalMemoizer>(memo));
}
template <typename Beta = uint64_t, typename KeyT, typename LaneT>
auto eval_interval(cmp_t, const KeyT & key, LaneT from, LaneT to)
{
return detail::incr::eval_cmp_interval_impl<Beta>(key, from, to);
}
// ---------------------------------------------------------------------------
// eval_full(target, key [, …])
// ---------------------------------------------------------------------------
template <std::size_t I, std::size_t N, typename KeyT,
typename OutputBuffer, typename IntervalMemoizer>
auto eval_full(out_t<I, N>, const KeyT & key, OutputBuffer && outbuf,
IntervalMemoizer && memo)
{
if constexpr (is_multilevel_key_v<KeyT>)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_full_impl<pref, I>(key,
std::forward<OutputBuffer>(outbuf),
std::forward<IntervalMemoizer>(memo));
}
else
{
return eval_full<I>(key, std::forward<OutputBuffer>(outbuf),
std::forward<IntervalMemoizer>(memo));
}
}
template <std::size_t I, std::size_t N, typename KeyT>
auto eval_full(out_t<I, N>, const KeyT & key)
{
if constexpr (is_multilevel_key_v<KeyT>)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_full_impl<pref, I>(key);
}
else
{
return eval_full<I>(key);
}
}
template <typename Beta = uint64_t, typename KeyT>
auto eval_full(cmp_t, const KeyT & key)
{
if (!key.has_cmp())
throw std::invalid_argument("eval_full(cmp): no comparison channel");
using lane_t = typename KeyT::integral_type;
const auto nbits = static_cast<std::size_t>(key.cmp().nbits);
const lane_t lo = 0;
const lane_t hi = (nbits >= 8 * sizeof(lane_t))
? static_cast<lane_t>(~lane_t{0})
: static_cast<lane_t>((lane_t{1} << nbits) - 1);
return detail::incr::eval_cmp_interval_impl<Beta>(key, lo, hi);
}
// ---------------------------------------------------------------------------
// eval_sequence(target, key, begin, end, buf [, path])
// ---------------------------------------------------------------------------
template <std::size_t I, std::size_t N, typename KeyT, typename ForwardIterator,
typename OutputBuffer,
typename PathMemoizer = basic_path_memoizer<KeyT>>
auto eval_sequence(out_t<I, N>, const KeyT & key, ForwardIterator begin,
ForwardIterator end, OutputBuffer && outbuf,
PathMemoizer && path = PathMemoizer{})
{
if constexpr (is_multilevel_key_v<KeyT>)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_sequence_impl<pref, I>(key, begin, end,
std::forward<OutputBuffer>(outbuf),
std::forward<PathMemoizer>(path));
}
else
{
return eval_sequence<I>(key, begin, end,
std::forward<OutputBuffer>(outbuf));
}
}
template <typename Beta = uint64_t, typename KeyT, typename ForwardIterator,
typename OutputBuffer,
typename PathMemoizer = basic_path_memoizer<KeyT>>
void eval_sequence(cmp_t, const KeyT & key, ForwardIterator begin,
ForwardIterator end, OutputBuffer && outbuf,
PathMemoizer && path = PathMemoizer{})
{
detail::incr::eval_cmp_sequence_impl<Beta>(key, begin, end,
std::forward<OutputBuffer>(outbuf),
std::forward<PathMemoizer>(path));
}
// ---------------------------------------------------------------------------
// make_output_buffer(target, …)
// ---------------------------------------------------------------------------
template <typename Beta = uint64_t, typename KeyT>
auto make_output_buffer(cmp_t, const KeyT & key, std::size_t n)
{
return detail::incr::make_output_buffer_for_cmp_impl<Beta>(key, n);
}
template <typename Beta = uint64_t, typename KeyT, typename LaneT>
auto make_output_buffer(cmp_t, const KeyT & key, LaneT from, LaneT to)
{
return detail::incr::make_output_buffer_for_cmp_interval_impl<Beta>(
key, from, to);
}
template <std::size_t I, std::size_t N, typename KeyT, typename LaneT>
auto make_output_buffer(out_t<I, N>, const KeyT & key, LaneT from, LaneT to)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::make_output_buffer_for_out_interval_impl<pref, I>(
key, from, to);
}
// ---------------------------------------------------------------------------
// eval_inner_product(target, key, from, to, weights [, memo])
//
// Point-slot inner product: same interior walk as `eval_interval(out<I>, …)`
// but each packed leaf is multiply-accumulated against a public weight vector
// instead of being materialized. Additive outputs sum `DPF_I(x)·w[x]`; XOR
// outputs (`bit` / `xor_wrapper`) xor `DPF_I(x) & w[x]`. Weights are indexed in
// the slot's lane domain, matching `eval_interval`'s destination layout.
//
// Cmp inner product: dot of the per-point comparison path-sum shares with the
// weights (no leaf MAC); the two parties' results reconstruct to the true dot.
// ---------------------------------------------------------------------------
namespace detail
{
namespace incr
{
template <typename OutputT, typename NodeT>
struct ml_ip_accum
{
static constexpr bool xor_mode =
std::is_same_v<OutputT, dpf::bit> || utils::is_xor_wrapper_v<OutputT>;
psnip_uint64_t acc = 0;
template <typename LeafT, typename W>
void mac(const LeafT & leaf, std::size_t base, std::size_t opl, W && w)
{
for (std::size_t p = 0; p < opl; ++p)
{
psnip_uint64_t val;
if constexpr (utils::is_packed_subbyte_v<OutputT>)
{
val = static_cast<psnip_uint64_t>(
dpf::extract_leaf<NodeT, OutputT>(leaf, p));
}
else
{
OutputT v;
std::memcpy(&v,
reinterpret_cast<const unsigned char *>(std::addressof(leaf))
+ p * sizeof(OutputT),
sizeof(v));
if constexpr (utils::is_xor_wrapper_v<OutputT>)
{
// `static_cast<psnip_uint64_t>(v)` is ambiguous for
// `xor_wrapper` (both `operator bool` and `operator T`
// are viable). Go through the concrete underlying bits.
val = static_cast<psnip_uint64_t>(v.data());
}
else
{
val = static_cast<psnip_uint64_t>(v);
}
}
const auto wt = static_cast<psnip_uint64_t>(w[base + p]);
if constexpr (xor_mode)
acc ^= (val & wt);
else if constexpr (utils::is_packed_subbyte_v<OutputT>)
{
constexpr auto mask
= (static_cast<psnip_uint64_t>(1)
<< utils::packed_lane_bits_v<OutputT>)
- 1;
acc = (acc + (val & mask) * (wt & mask)) & mask;
}
else
acc += val * wt;
}
}
OutputT finish() const
{
if constexpr (std::is_same_v<OutputT, dpf::bit>)
return OutputT{static_cast<bool>(acc & 1)};
else if constexpr (utils::is_packed_subbyte_v<OutputT>)
return static_cast<OutputT>(acc);
else if constexpr (utils::is_xor_wrapper_v<OutputT>)
return OutputT{static_cast<typename OutputT::value_type>(acc)};
else
return static_cast<OutputT>(acc);
}
};
template <std::size_t N, std::size_t I, typename KeyT, typename LaneT,
typename Weights, typename IntervalMemoizer>
auto eval_out_inner_product_impl(const KeyT & dpf, LaneT from, LaneT to,
Weights && weights, IntervalMemoizer && memoizer)
{
using key_type = KeyT;
static_assert(key_type::meta[I].prefix == N,
"out inner product: N does not match output I");
using output_type = typename key_type::template concrete_output_type<I>;
using exterior_node = typename key_type::exterior_node;
using integral_type = typename key_type::integral_type;
constexpr auto opl = key_type::template outputs_per_leaf_of<I>;
constexpr auto lg_opl = key_type::template lg_outputs_per_leaf_of<I>;
constexpr auto to_level = key_type::meta[I].tree_level;
constexpr auto to_int = utils::to_integral_type<LaneT>{};
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
integral_type from_node = utils::leaf_node_floor(
static_cast<integral_type>(to_int(from)), lg_opl);
integral_type to_node = utils::leaf_node_ceil_exclusive(
static_cast<integral_type>(to_int(to)), lg_opl);
const auto segs = utils::split_leaf_nodes(from_node, to_node, to_level);
ml_ip_accum<output_type, exterior_node> acc{};
std::size_t start = 0;
for (std::size_t s = 0; s < segs.n; ++s)
{
const auto & seg = segs.seg[s];
internal::eval_out_interval_interior<N, I>(dpf, seg.from_node,
seg.to_node, memoizer);
auto * nodes = memoizer[to_level];
const std::size_t count =
static_cast<std::size_t>(seg.to_node - seg.from_node);
for (std::size_t j = 0; j < count; ++j)
{
auto leaf = dpf.template traverse_exterior<I>(nodes[j]);
acc.mac(leaf, (start + j) * opl, opl, weights);
}
start += seg.count;
}
return acc.finish();
}
template <typename Beta = uint64_t, typename KeyT, typename LaneT,
typename Weights>
Beta eval_cmp_inner_product_impl(const KeyT & dpf, LaneT from, LaneT to,
Weights && weights)
{
if (!dpf.has_cmp())
throw std::invalid_argument("cmp inner product: no comparison channel");
if (!dpf.cmp_assigned())
throw std::invalid_argument(
"cmp inner product: wildcard payload not assigned (call assign_cmp)");
constexpr auto to_int = utils::to_integral_type<LaneT>{};
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
const auto nbits = static_cast<std::size_t>(dpf.cmp().nbits);
const uint64_t mask = dpf.cmp().mask;
using integral = typename KeyT::integral_type;
const auto a = static_cast<integral>(to_int(from));
const auto b = static_cast<integral>(to_int(to));
const auto count = cmp_inclusive_count(a, b);
constexpr std::size_t stop =
KeyT::cmp_depth == 0 ? KeyT::depth : KeyT::cmp_depth;
detail::incr::cmp_full_interval_memo<KeyT, stop> memo{count};
detail::incr::eval_cmp_interval_impl_interior(dpf, a, cmp_exclusive_end(b),
nbits, memo);
uint64_t dot = 0;
for (std::size_t i = 0; i < count; ++i)
{
const auto q = static_cast<integral>(a + static_cast<integral>(i));
const uint64_t raw =
detail::incr::eval_cmp_from_interval_memo(dpf, q, a, nbits, memo);
const uint64_t wt = static_cast<uint64_t>(weights[i]) & mask;
dot = (dot + ((raw & mask) * wt)) & mask;
}
return detail::dcf_impl::u64_to_beta<Beta>(dot);
}
} // namespace incr
} // namespace detail
template <std::size_t I, std::size_t N, typename KeyT, typename LaneT,
typename Weights, typename IntervalMemoizer,
std::enable_if_t<is_multilevel_key_v<KeyT>, bool> = true>
auto eval_inner_product(out_t<I, N>, const KeyT & key, LaneT from, LaneT to,
Weights && weights, IntervalMemoizer && memo)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_inner_product_impl<pref, I>(key, from, to,
std::forward<Weights>(weights),
std::forward<IntervalMemoizer>(memo));
}
template <std::size_t I, std::size_t N, typename KeyT, typename LaneT,
typename Weights,
std::enable_if_t<is_multilevel_key_v<KeyT>, bool> = true>
auto eval_inner_product(out_t<I, N>, const KeyT & key, LaneT from, LaneT to,
Weights && weights)
{
auto memo = make_basic_interval_memoizer<KeyT, I>(from, to);
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
return detail::incr::eval_out_inner_product_impl<pref, I>(key, from, to,
std::forward<Weights>(weights), memo);
}
template <typename Beta = uint64_t, typename KeyT, typename LaneT,
typename Weights>
Beta eval_inner_product(cmp_t, const KeyT & key, LaneT from, LaneT to,
Weights && weights)
{
return detail::incr::eval_cmp_inner_product_impl<Beta>(key, from, to,
std::forward<Weights>(weights));
}
// ---------------------------------------------------------------------------
// eval_sequence_breadth_first(out<I>, key, begin, end [, outbuf])
//
// Breadth-first sequence eval that stops the interior walk at `meta[I]
// .tree_level` (the leaf level of slot `I`) instead of the full key depth.
// `begin`/`end` are a *sorted* range of lane points in `[0, 2^N)` (top-N-bit
// prefixes); the result is written output-only, one value per query point in
// query order (`outbuf[i]` is the output for the `i`-th query).
// ---------------------------------------------------------------------------
namespace detail
{
namespace incr
{
template <std::size_t N, std::size_t I, typename KeyT,
typename ForwardIterator, typename OutputBuffer>
void eval_out_sequence_breadth_first_impl(const KeyT & dpf,
ForwardIterator begin, ForwardIterator end, OutputBuffer && outbuf)
{
using key_type = KeyT;
static_assert(key_type::meta[I].prefix == N,
"breadth-first out sequence: N does not match output I");
using input_type = typename key_type::input_type;
using node_type = typename key_type::interior_node;
using exterior_node = typename key_type::exterior_node;
using output_type = typename key_type::template concrete_output_type<I>;
constexpr std::size_t stop = key_type::meta[I].tree_level;
constexpr std::size_t lg_opl = key_type::template lg_outputs_per_leaf_of<I>;
constexpr std::size_t opl = std::size_t{1} << lg_opl;
if (HEDLEY_UNLIKELY(!std::is_sorted(begin, end)))
throw std::runtime_error("breadth-first sequence: list must be sorted");
if (begin == end)
return;
using allocator = aligned_allocator<node_type>;
allocator alloc{};
const std::size_t nseq = static_cast<std::size_t>(std::distance(begin, end));
auto memo = alloc.allocate_unique_ptr(nseq * 2);
if (HEDLEY_UNLIKELY(memo == nullptr))
throw std::bad_alloc{};
input_type mask = static_cast<input_type>(input_type{1} << (N - 1));
bool curhalf = (stop ^ 1) & 1;
memo[static_cast<std::size_t>(!curhalf) * nseq + 0] = dpf.root();
std::list<ForwardIterator> splits{begin, end};
std::size_t level_index = 1;
auto step = [&]() {
std::size_t i = 0, j = 0;
const node_type cw[2] = {
dpf.correction_word(level_index - 1, 0),
dpf.correction_word(level_index - 1, 1)};
const std::size_t cur = static_cast<std::size_t>(curhalf) * nseq;
const std::size_t prv = static_cast<std::size_t>(!curhalf) * nseq;
for (auto upper = std::begin(splits), lower = upper++;
upper != std::end(splits); lower = upper++)
{
auto it = std::upper_bound(*lower, *upper, mask,
[](auto a, auto b) { return static_cast<bool>(a & b); });
if (it == *lower)
{
memo[cur + i++] = key_type::traverse_interior(
memo[prv + j++], cw[1], 1);
}
else if (it == *upper)
{
memo[cur + i++] = key_type::traverse_interior(
memo[prv + j++], cw[0], 0);
}
else
{
auto kids = key_type::traverse_interior01(memo[prv + j++],
cw[0], cw[1]);
memo[cur + i++] = kids[0];
memo[cur + i++] = kids[1];
splits.insert(upper, it);
}
}
};
for (; level_index <= stop;
++level_index, mask >>= 1, curhalf = !curhalf)
step();
auto * buf = memo.get(); // deepest built level (stop) lands in half 0
auto curr = begin, prev = begin;
std::size_t j = 0;
for (std::size_t i = 0; i < nseq; ++i)
{
if (i > 0
&& (static_cast<input_type>(*curr) >> lg_opl)
!= (static_cast<input_type>(*prev) >> lg_opl))
++j;
auto leaf = dpf.template traverse_exterior<I>(buf[j]);
const std::size_t off =
static_cast<std::size_t>(static_cast<input_type>(*curr) & (opl - 1));
auto v = dpf::extract_leaf<exterior_node, output_type>(leaf, off);
if constexpr (is_party_key_v<KeyT>)
outbuf[i] = subtractive_share<output_type, party_of_v<KeyT>>::from_raw(v);
else
outbuf[i] = v;
prev = curr++;
}
}
} // namespace incr
} // namespace detail
template <std::size_t I, std::size_t N, typename KeyT,
typename ForwardIterator, typename OutputBuffer,
std::enable_if_t<is_multilevel_key_v<KeyT>, bool> = true>
void eval_sequence_breadth_first(out_t<I, N>, const KeyT & key,
ForwardIterator begin, ForwardIterator end, OutputBuffer && outbuf)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
detail::incr::eval_out_sequence_breadth_first_impl<pref, I>(key, begin, end,
std::forward<OutputBuffer>(outbuf));
}
template <std::size_t I, std::size_t N, typename KeyT,
typename ForwardIterator,
std::enable_if_t<is_multilevel_key_v<KeyT>, bool> = true>
auto eval_sequence_breadth_first(out_t<I, N>, const KeyT & key,
ForwardIterator begin, ForwardIterator end)
{
using output_type = typename KeyT::template concrete_output_type<I>;
const std::size_t n = static_cast<std::size_t>(std::distance(begin, end));
dpf::output_buffer<leaf_buffer_elem_t<KeyT, output_type>> buf(n);
eval_sequence_breadth_first(out_t<I, N>{}, key, begin, end, buf);
return buf;
}
/// Build a sequence recipe stopped at slot `I`'s tree level (prefix domain).
template <std::size_t I, std::size_t N, typename KeyT, typename ForwardIterator,
std::enable_if_t<is_multilevel_key_v<KeyT>, bool> = true>
auto make_sequence_recipe(out_t<I, N>, const KeyT & key, ForwardIterator begin,
ForwardIterator end)
{
constexpr auto pref = detail::resolved_out_prefix<I, N, KeyT>();
using input_type = typename KeyT::input_type;
constexpr auto stop = KeyT::meta[I].tree_level;
constexpr auto lg = KeyT::template lg_outputs_per_leaf_of<I>;
const input_type lane_msb =
static_cast<input_type>(input_type{1} << (pref - 1));
(void)key;
return make_sequence_recipe_at<stop, lg, input_type>(lane_msb, begin, end);
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_UNIFIED_HPP__

638
include/dpf/geneval.hpp Normal file
View file

@ -0,0 +1,638 @@
/// @file dpf/geneval.hpp
/// @brief Fused generation and evaluation (Doerner–Shelat on the eval trie).
/// @details `make_dpf` / `make_dpf_doerner_shelat` build a reusable key, then
/// `eval_*` walks it. `geneval_*` does both at once: one correction
/// word per level, opened from the XOR-reduction of the nodes the
/// public query actually expands. While the secret path's parent is
/// still in that trie the word matches the reusable key byte for
/// byte (same roots, same Beaver tape). After the path leaves, the
/// word is uniform and later outputs still reconstruct — off-path
/// nodes are identical across the two parties, so a dummy word
/// cancels.
///
/// A wildcard-input call takes additive shares of the real point and
/// a public query. It samples a random target, runs geneval there,
/// and shifts the query by `target - x`, which is what
/// `offset_x` does after a wildcard key is bound to `x`.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_GENEVAL_HPP__
#define LIBDPF_INCLUDE_DPF_GENEVAL_HPP__
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "hedley/hedley.h"
#include "simde/simde/x86/avx2.h"
#include "dpf/aligned_allocator.hpp"
#include "dpf/doerner_shelat.hpp"
#include "dpf/leaf_node.hpp"
namespace dpf
{
/// Tag for a geneval whose point is known only as additive shares.
struct wildcard_input_t
{
};
inline constexpr wildcard_input_t wildcard_input{};
/// Shares and the correction words opened along the query trie.
/// `correction_words[i]` / `correction_advice[i]` match a reusable key at
/// the same target for every `i < live_levels`. `leaf_live` means the
/// target's leaf was in the trie, so `leaf` is that key's leaf word.
template <typename Output, typename Leaf>
struct geneval_result
{
std::vector<Output> party0;
std::vector<Output> party1;
std::vector<simde__m128i, aligned_allocator<simde__m128i>> correction_words;
std::vector<uint8_t> correction_advice;
std::size_t live_levels = 0;
bool leaf_live = false;
Leaf leaf{};
};
namespace detail
{
template <typename T>
HEDLEY_ALWAYS_INLINE
T geneval_mod_add(T a, T b) noexcept
{
using U = std::make_unsigned_t<T>;
U sum = static_cast<U>(static_cast<U>(a) + static_cast<U>(b));
T out;
std::memcpy(&out, &sum, sizeof(out));
return out;
}
template <typename T>
HEDLEY_ALWAYS_INLINE
T geneval_mod_sub(T a, T b) noexcept
{
using U = std::make_unsigned_t<T>;
U diff = static_cast<U>(static_cast<U>(a) - static_cast<U>(b));
T out;
std::memcpy(&out, &diff, sizeof(out));
return out;
}
template <typename T>
T geneval_flipped(T x)
{
utils::flip_msb_if_signed_integral(x);
return x;
}
/// Leaf-node id of an already MSB-flipped input. The id is the high
/// `depth` bits; the low `lg(outputs_per_leaf)` bits select the lane.
template <typename Dpf>
uint64_t geneval_leaf_id(typename Dpf::input_type x)
{
return static_cast<uint64_t>(utils::get_from_node<Dpf>(x));
}
inline uint64_t geneval_prefix(uint64_t leaf, std::size_t depth, std::size_t bits)
{
if (bits == 0)
return 0;
if (bits >= depth)
return leaf;
return leaf >> (depth - bits);
}
inline bool geneval_any_prefix(const std::vector<uint64_t> & leaves,
std::size_t depth, uint64_t id, std::size_t bits)
{
if (leaves.empty())
return false;
if (bits == 0)
return true;
const std::size_t sh = depth - bits;
const uint64_t lo = (sh >= 64) ? 0 : (id << sh);
auto it = std::lower_bound(leaves.begin(), leaves.end(), lo);
if (it == leaves.end())
return false;
return geneval_prefix(*it, depth, bits) == id;
}
template <typename Output, typename Leaf>
geneval_result<Output, Leaf> geneval_empty_result()
{
geneval_result<Output, Leaf> out;
std::memset(&out.leaf, 0, sizeof(out.leaf));
return out;
}
template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng>
auto geneval_run(InputT x0, InputT x1, const std::vector<InputT> & queries,
RootSampler & root_sampler, PadRng & pads, OutputT y)
{
static_assert(std::is_integral_v<InputT>,
"geneval input shares are an integral domain");
static_assert(!dpf::is_wildcard_v<OutputT>,
"geneval output is concrete; assign a wildcard leaf on a key");
static_assert(utils::bitlength_of_v<InputT> <= 64,
"geneval leaf ids are 64-bit");
using dpf_type = utils::dpf_type_t<InteriorPRG, ExteriorPRG, InputT, OutputT>;
using node = typename dpf_type::interior_node;
using leaf_node = leaf_node_t<node, OutputT>;
constexpr std::size_t depth = dpf_type::depth;
if (queries.empty())
return geneval_empty_result<OutputT, leaf_node>();
if (queries.size() > (std::size_t{1} << 22))
throw std::length_error("geneval query is too large");
InputT x0c = x0;
InputT x1c = x1;
utils::flip_msb_if_signed_integral(x0c);
const InputT alpha = utils::xor_input_shares(x0c, x1c);
std::vector<InputT> flipped;
flipped.reserve(queries.size());
std::vector<uint64_t> leaves;
leaves.reserve(queries.size());
for (const InputT & q : queries)
{
InputT fq = geneval_flipped(q);
flipped.push_back(fq);
leaves.push_back(geneval_leaf_id<dpf_type>(fq));
}
std::vector<uint64_t> unique_leaves = leaves;
std::sort(unique_leaves.begin(), unique_leaves.end());
unique_leaves.erase(std::unique(unique_leaves.begin(), unique_leaves.end()),
unique_leaves.end());
if (unique_leaves.size() > (std::size_t{1} << 20))
throw std::length_error("geneval trie is too large");
const uint64_t secret_leaf = geneval_leaf_id<dpf_type>(alpha);
local_cw_protocol<PadRng> proto{pads};
constexpr auto to_int = utils::to_integral_type<InputT>{};
const node root0 = dpf::unset_lo_bit(static_cast<node>(root_sampler()));
const node root1 = dpf::set_lo_bit(static_cast<node>(root_sampler()));
struct slot
{
uint64_t id;
node s0;
node s1;
};
std::vector<slot> frontier;
frontier.push_back(slot{0, root0, root1});
geneval_result<OutputT, leaf_node> result;
std::memset(&result.leaf, 0, sizeof(result.leaf));
result.correction_words.reserve(depth);
result.correction_advice.reserve(depth);
auto mask = dpf_type::msb_mask;
bool still_live = true;
for (std::size_t level = 0; level < depth; ++level, mask >>= 1)
{
const uint8_t bit0 = static_cast<uint8_t>(!!(to_int(mask) & to_int(x0c)));
const uint8_t bit1 = static_cast<uint8_t>(!!(to_int(mask) & to_int(x1c)));
const uint64_t parent_id = geneval_prefix(secret_leaf, depth, level);
node L0 = simde_mm_setzero_si128();
node R0 = simde_mm_setzero_si128();
node L1 = simde_mm_setzero_si128();
node R1 = simde_mm_setzero_si128();
bool level_live = false;
struct exp
{
uint64_t id;
node s0, s1, L0, R0, L1, R1;
};
std::vector<exp> exps;
exps.reserve(frontier.size());
for (const slot & n : frontier)
{
if (n.id == parent_id)
level_live = true;
const auto c0 = InteriorPRG::eval01(dpf::unset_lo_2bits(n.s0));
const auto c1 = InteriorPRG::eval01(dpf::unset_lo_2bits(n.s1));
L0 = ds_xor(L0, c0[0]);
R0 = ds_xor(R0, c0[1]);
L1 = ds_xor(L1, c1[0]);
R1 = ds_xor(R1, c1[1]);
exps.push_back(exp{n.id, n.s0, n.s1, c0[0], c0[1], c1[0], c1[1]});
}
node cw;
uint8_t advice;
if (still_live && level_live)
{
auto blinds = proto.prepare_level(L0, R0, bit0, L1, R1, bit1);
auto opened = proto.open_cw(blinds);
cw = opened.first;
advice = opened.second;
++result.live_levels;
}
else
{
still_live = false;
cw = pads.block();
const uint8_t t0 = static_cast<uint8_t>(pads.bit() & 1u);
const uint8_t t1 = static_cast<uint8_t>(pads.bit() & 1u);
advice = static_cast<uint8_t>((t1 << 1) | t0);
}
result.correction_words.push_back(cw);
result.correction_advice.push_back(advice);
const node cw0 = dpf::set_lo_bit(cw, advice & 1u);
const node cw1 = dpf::set_lo_bit(cw, (advice >> 1) & 1u);
const std::size_t child_bits = level + 1;
std::vector<slot> next;
next.reserve(exps.size() * 2);
for (const exp & e : exps)
{
const uint64_t left = e.id << 1;
const uint64_t right = left | 1ull;
if (geneval_any_prefix(unique_leaves, depth, left, child_bits))
{
next.push_back(slot{left,
dpf::xor_if_lo_bit(e.L0, cw0, e.s0),
dpf::xor_if_lo_bit(e.L1, cw0, e.s1)});
}
if (geneval_any_prefix(unique_leaves, depth, right, child_bits))
{
next.push_back(slot{right,
dpf::xor_if_lo_bit(e.R0, cw1, e.s0),
dpf::xor_if_lo_bit(e.R1, cw1, e.s1)});
}
}
frontier = std::move(next);
}
result.leaf_live = geneval_any_prefix(unique_leaves, depth, secret_leaf, depth);
if (result.leaf_live)
{
const slot * on = nullptr;
for (const slot & n : frontier)
{
if (n.id == secret_leaf)
{
on = &n;
break;
}
}
if (on == nullptr)
throw std::logic_error("geneval: secret leaf missing from trie");
const bool sign0 = dpf::get_lo_bit(on->s0);
auto built = dpf::make_leaves<ExteriorPRG>(alpha,
dpf::unset_lo_2bits(on->s0), dpf::unset_lo_2bits(on->s1), sign0,
std::size_t{0}, y);
result.leaf = std::get<0>(built.first.first);
}
result.party0.reserve(flipped.size());
result.party1.reserve(flipped.size());
for (std::size_t i = 0; i < flipped.size(); ++i)
{
const uint64_t id = leaves[i];
const slot * n = nullptr;
for (const slot & s : frontier)
{
if (s.id == id)
{
n = &s;
break;
}
}
if (n == nullptr)
throw std::logic_error("geneval: query leaf missing from trie");
auto share0 = dpf_type::template traverse_exterior<0>(n->s0, result.leaf);
auto share1 = dpf_type::template traverse_exterior<0>(n->s1, result.leaf);
const auto lane = static_cast<std::size_t>(to_int(flipped[i]));
result.party0.push_back(extract_leaf<node, OutputT>(share0, lane));
result.party1.push_back(extract_leaf<node, OutputT>(share1, lane));
}
return result;
}
template <typename InputT>
InputT geneval_from_bits(uint64_t bits)
{
using U = std::make_unsigned_t<InputT>;
U u = static_cast<U>(bits);
InputT out;
std::memcpy(&out, &u, sizeof(out));
return out;
}
template <typename InputT>
bool geneval_out_of_order(InputT from, InputT to)
{
// Numeric order. An unsigned compare of a signed value treats a negative
// `from` as larger than a positive `to`, and would reject `[-1, 1]`.
if constexpr (std::is_signed_v<InputT>)
return from > to;
else
return utils::to_integral_type<InputT>{}(from)
> utils::to_integral_type<InputT>{}(to);
}
template <typename InputT>
std::vector<InputT> geneval_full_domain()
{
constexpr std::size_t bitlen = utils::bitlength_of_v<InputT>;
if (bitlen > 20)
throw std::length_error("geneval_full domain is too large");
const uint64_t n = uint64_t{1} << bitlen;
std::vector<InputT> qs(static_cast<std::size_t>(n));
// Index `i` is the input's bit pattern, including the sign bit. A
// narrowing cast of `i` to a signed type is implementation-defined.
for (uint64_t i = 0; i < n; ++i)
qs[static_cast<std::size_t>(i)] = geneval_from_bits<InputT>(i);
return qs;
}
template <typename InputT>
std::vector<InputT> geneval_inclusive(InputT from, InputT to)
{
if (geneval_out_of_order(from, to))
{
throw std::invalid_argument("geneval_interval: from > to");
}
std::vector<InputT> qs;
InputT q = from;
const InputT one = utils::make_from_integral_value<InputT>{}(1);
for (;;)
{
qs.push_back(q);
if (q == to)
break;
q = geneval_mod_add(q, one);
if (qs.size() > (std::size_t{1} << 22))
throw std::length_error("geneval_interval is too large");
}
return qs;
}
template <typename InputT, typename TargetSampler>
InputT geneval_sample_target(TargetSampler & sample)
{
return static_cast<InputT>(sample());
}
template <typename InputT>
std::vector<InputT> geneval_shift_all(const std::vector<InputT> & qs, InputT delta)
{
std::vector<InputT> out;
out.reserve(qs.size());
for (const InputT & q : qs)
out.push_back(geneval_mod_add(q, delta));
return out;
}
} // namespace detail
/// Geneval at one public point. The secret point is `x0 XOR x1`.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_point(InputT x0, InputT x1, InputT query,
ds_randomness<RootSampler, PadRng> rng, OutputT y)
{
return detail::geneval_run<InteriorPRG, ExteriorPRG>(x0, x1,
std::vector<InputT>{query}, rng.root, rng.pad, y);
}
/// Geneval on the inclusive interval `[from, to]`.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_interval(InputT x0, InputT x1, InputT from, InputT to,
ds_randomness<RootSampler, PadRng> rng, OutputT y)
{
return detail::geneval_run<InteriorPRG, ExteriorPRG>(x0, x1,
detail::geneval_inclusive(from, to), rng.root, rng.pad, y);
}
/// Geneval on the whole domain. Refuses a domain above 2^20 inputs.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_full(InputT x0, InputT x1,
ds_randomness<RootSampler, PadRng> rng, OutputT y)
{
return detail::geneval_run<InteriorPRG, ExteriorPRG>(x0, x1,
detail::geneval_full_domain<InputT>(), rng.root, rng.pad, y);
}
/// Geneval on a public sequence, in the order given.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng,
typename ForwardIterator>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_sequence(InputT x0, InputT x1, ForwardIterator begin,
ForwardIterator end, ds_randomness<RootSampler, PadRng> rng, OutputT y)
{
std::vector<InputT> qs(begin, end);
return detail::geneval_run<InteriorPRG, ExteriorPRG>(x0, x1,
std::move(qs), rng.root, rng.pad, y);
}
/// Wildcard-input geneval. `x0 + x1` is the real point (additive shares).
/// `sample_target()` is the random DPF target; the public query is shifted
/// by `target - (x0 + x1)` before the walk.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng,
typename TargetSampler>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_point(wildcard_input_t, InputT x0, InputT x1, InputT query,
ds_randomness<RootSampler, PadRng> rng, TargetSampler sample_target,
OutputT y)
{
const InputT alpha = detail::geneval_sample_target<InputT>(sample_target);
const InputT delta = detail::geneval_mod_sub(alpha,
detail::geneval_mod_add(x0, x1));
const InputT shifted = detail::geneval_mod_add(query, delta);
InputT zero{};
return detail::geneval_run<InteriorPRG, ExteriorPRG>(zero, alpha,
std::vector<InputT>{shifted}, rng.root, rng.pad, y);
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_point(wildcard_input_t, InputT x0, InputT x1, InputT query,
ds_randomness<RootSampler, PadRng> rng, OutputT y)
{
return geneval_point<InteriorPRG, ExteriorPRG>(wildcard_input, x0, x1, query,
std::move(rng), [] { return dpf::uniform_sample<InputT>(); }, y);
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng,
typename TargetSampler>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_interval(wildcard_input_t, InputT x0, InputT x1, InputT from,
InputT to, ds_randomness<RootSampler, PadRng> rng, TargetSampler sample_target,
OutputT y)
{
const InputT alpha = detail::geneval_sample_target<InputT>(sample_target);
const InputT delta = detail::geneval_mod_sub(alpha,
detail::geneval_mod_add(x0, x1));
auto shifted = detail::geneval_shift_all(
detail::geneval_inclusive(from, to), delta);
InputT zero{};
return detail::geneval_run<InteriorPRG, ExteriorPRG>(zero, alpha,
std::move(shifted), rng.root, rng.pad, y);
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_interval(wildcard_input_t, InputT x0, InputT x1, InputT from,
InputT to, ds_randomness<RootSampler, PadRng> rng, OutputT y)
{
return geneval_interval<InteriorPRG, ExteriorPRG>(wildcard_input, x0, x1,
from, to, std::move(rng), [] { return dpf::uniform_sample<InputT>(); }, y);
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng,
typename TargetSampler>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_full(wildcard_input_t, InputT x0, InputT x1,
ds_randomness<RootSampler, PadRng> rng, TargetSampler sample_target, OutputT y)
{
const InputT alpha = detail::geneval_sample_target<InputT>(sample_target);
const InputT delta = detail::geneval_mod_sub(alpha,
detail::geneval_mod_add(x0, x1));
InputT zero{};
auto full = detail::geneval_run<InteriorPRG, ExteriorPRG>(zero, alpha,
detail::geneval_full_domain<InputT>(), rng.root, rng.pad, y);
constexpr auto to_int = utils::to_integral_type<InputT>{};
const std::size_t n = full.party0.size();
std::vector<OutputT> p0(n), p1(n);
for (std::size_t i = 0; i < n; ++i)
{
InputT q = detail::geneval_from_bits<InputT>(i);
InputT s = detail::geneval_mod_add(q, delta);
const std::size_t si = static_cast<std::size_t>(to_int(s));
p0[i] = full.party0[si];
p1[i] = full.party1[si];
}
full.party0 = std::move(p0);
full.party1 = std::move(p1);
return full;
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_full(wildcard_input_t, InputT x0, InputT x1,
ds_randomness<RootSampler, PadRng> rng, OutputT y)
{
return geneval_full<InteriorPRG, ExteriorPRG>(wildcard_input, x0, x1,
std::move(rng), [] { return dpf::uniform_sample<InputT>(); }, y);
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng,
typename ForwardIterator,
typename TargetSampler>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_sequence(wildcard_input_t, InputT x0, InputT x1,
ForwardIterator begin, ForwardIterator end,
ds_randomness<RootSampler, PadRng> rng, TargetSampler sample_target, OutputT y)
{
const InputT alpha = detail::geneval_sample_target<InputT>(sample_target);
const InputT delta = detail::geneval_mod_sub(alpha,
detail::geneval_mod_add(x0, x1));
std::vector<InputT> qs(begin, end);
auto shifted = detail::geneval_shift_all(qs, delta);
InputT zero{};
return detail::geneval_run<InteriorPRG, ExteriorPRG>(zero, alpha,
std::move(shifted), rng.root, rng.pad, y);
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT,
typename RootSampler,
typename PadRng,
typename ForwardIterator>
HEDLEY_WARN_UNUSED_RESULT
auto geneval_sequence(wildcard_input_t, InputT x0, InputT x1,
ForwardIterator begin, ForwardIterator end,
ds_randomness<RootSampler, PadRng> rng, OutputT y)
{
return geneval_sequence<InteriorPRG, ExteriorPRG>(wildcard_input, x0, x1,
begin, end, std::move(rng), [] { return dpf::uniform_sample<InputT>(); }, y);
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_GENEVAL_HPP__

2175
include/dpf/incremental.hpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,542 @@
/// @file dpf/interval_memoizer.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @author Christopher Jiang <christopher.jiang@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_INTERVAL_MEMOIZER_HPP__
#define LIBDPF_INCLUDE_DPF_INTERVAL_MEMOIZER_HPP__
#include "hedley/hedley.h"
#include <cstddef>
#include <cstring>
#include <type_traits>
#include <functional>
#include <algorithm>
#include <new>
#include <limits>
#include <stdexcept>
#include <optional>
#include <array>
#include "dpf/dpf_key.hpp"
#include "dpf/secret_share.hpp"
namespace dpf
{
/// Ping-pong pivot math underflows at 0 leaves. Keep a one-node slab so the
/// root still has a place to land; callers never walk a 0-leaf interval.
inline std::size_t interval_memoizer_slots(std::size_t output_len)
{
return output_len == 0 ? std::size_t{1} : output_len;
}
/// Interval memoizers key on the underlying DPF key type (same rule as path
/// memoizers): a memoizer built from `party_key<0, Key>` also accepts
/// `party_key<1, Key>` and bare `Key`.
template <typename DpfKey>
using interval_memoizer_key_t = unwrap_party_key_t<DpfKey>;
template <typename DpfKey,
typename ReturnT = typename interval_memoizer_key_t<DpfKey>::interior_node *>
struct interval_memoizer_base
{
public:
using dpf_type = interval_memoizer_key_t<DpfKey>;
using integral_type = typename dpf_type::integral_type;
using return_type = ReturnT;
using iterator_type = return_type;
using node_type = typename dpf_type::interior_node;
// level 0 should access the root
// level goes up to (and including) depth
virtual return_type operator[](std::size_t) const noexcept = 0;
// iterators should access most recently completed level
virtual return_type begin() const noexcept = 0;
virtual return_type end() const noexcept = 0;
virtual std::size_t assign_interval(const dpf_type & dpf, integral_type new_from, integral_type new_to)
{
static constexpr auto complement_of = std::bit_not{};
if (dpf_.has_value() == false
|| std::memcmp(&dpf_root_, &dpf.root(), sizeof(node_type)) != 0
|| std::memcmp(&dpf_common_part_hash_, &dpf.common_part_hash(), sizeof(digest_type)) != 0
|| from_.value_or(complement_of(new_from)) != new_from
|| to_.value_or(complement_of(new_to)) != new_to)
{
if (new_to - new_from > output_length)
{
throw std::length_error("size of new interval is too large for memoizer");
}
this->operator[](0)[0] = dpf.root();
dpf_ = std::cref(dpf);
dpf_root_ = dpf.root();
dpf_common_part_hash_ = dpf.common_part_hash();
from_ = new_from;
to_ = new_to;
level_index = 1;
}
return level_index;
}
std::size_t advance_level()
{
return ++level_index;
}
std::size_t get_nodes_at_level() const
{
return get_nodes_at_level(level_index, from_.value_or(0), to_.value_or(0));
}
std::size_t get_nodes_at_level(std::size_t level) const
{
return get_nodes_at_level(level, from_.value_or(0), to_.value_or(0));
}
static std::size_t get_nodes_at_level(std::size_t level, integral_type from_node, integral_type to_node)
{
// Algorithm explanation:
// Input:
// * offset - (derived from depth and level, note that level of -1 represents the root of the tree)
// * range of nodes - [from_node, to_node)
//
// Observation 1:
// For any level, knowing the range [from, to) allows one to calculate the number of nodes at that level
// as (to - from).
//
// Observation 2:
// If the range were stated as [from_0, to_0] for an offset 0, then [from_n, to_n] = [from_0 >> n, to_0 >> n]
// where >> is the bitshift operator. This is because the bits representing a node also represent the path
// taken in a binary tree to get to that node. Since from_0 and to_0 are both inclusive bounds, then their
// parent nodes must also be inclusive bounds for the next level up. These nodes can be found by simply removing
// the LSB from from_0 and to_0. The same can be done for parents further up the tree.
//
// Putting it together:
// * to_node-1 converts an excluded node to an included node
// * bit shifting as explained in observation 2
// * add 1 since observation 1 is for an excluded end point whereas now both end points are included
std::size_t offset = depth - level;
return utils::shift_right(to_node - integral_type{1}, offset)
- utils::shift_right(from_node, offset) + 1;
}
protected:
static constexpr auto depth = dpf_type::depth;
std::size_t output_length;
std::size_t level_index; // indicates current level being built
explicit interval_memoizer_base(std::size_t output_len)
: dpf_{std::nullopt},
from_{std::nullopt},
to_{std::nullopt},
output_length{output_len},
level_index{0}
{ }
private:
std::optional<std::reference_wrapper<const dpf_type>> dpf_;
node_type dpf_root_;
digest_type dpf_common_part_hash_;
std::optional<integral_type> from_;
std::optional<integral_type> to_;
};
template <typename DpfKey,
typename Allocator = aligned_allocator<
typename interval_memoizer_key_t<DpfKey>::interior_node>>
struct basic_interval_memoizer final : public interval_memoizer_base<DpfKey>
{
private:
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using parent = interval_memoizer_base<DpfKey>;
HEDLEY_PRAGMA(GCC diagnostic pop)
public:
using unique_ptr = typename Allocator::unique_ptr;
using return_type = typename interval_memoizer_key_t<DpfKey>::interior_node *;
using parent::depth;
using parent::level_index;
using parent::get_nodes_at_level;
// See comment for full_tree_interval_memoizer::initialize_endpoints() for
// general explanation of derivation for "nodes at previous level".
// When creating the final level of interior nodes from the previous level,
// care must be taken not to overwrite the previous level until the relevant
// nodes have been used to generate the new level. This means the pivot must
// be selected to push the previous level as far to the end of the buffer as
// possible.
// For n nodes in the final level:
// n odd => (n+1)/2 nodes on previous level
// => pivot = n-(n+1)/2 = (n-1)/2 = n/2-1/2 = floor(n/2)
// n even => n/2 OR (n+2)/2 nodes on previous level
// => pivot = n-(n+2)/2 = (n-2)/2 = n/2-1
// unified => floor(n/2)-1+(n%2) = (n>>1)+(n&1)-1
// In general, each previous level has roughly one half the nodes, but this is
// not true for some small n, which can stay constant up to the root.
// To handle this, take the maximum between the unified calculation shown
// and the number of nodes two levels up from the final level.
// For n nodes in the final level:
// at most ((n+2)/2+2)/2 = n+6>>2 nodes two levels up
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
explicit basic_interval_memoizer(std::size_t output_len, Allocator alloc = Allocator{})
: parent::interval_memoizer_base(output_len),
pivot{std::max((interval_memoizer_slots(output_len)>>1)
+(interval_memoizer_slots(output_len)&1)-1,
(interval_memoizer_slots(output_len) + 6) >> 2)},
buf{alloc.allocate_unique_ptr(
pivot+((interval_memoizer_slots(output_len)+2)>>1))}
{
if (HEDLEY_UNLIKELY(buf == nullptr)) throw std::bad_alloc{};
}
HEDLEY_PRAGMA(GCC diagnostic pop)
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
return_type operator[](std::size_t level) const noexcept override
{
bool b = (depth ^ level) & 1;
return Allocator::assume_aligned(&buf[b*pivot]);
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
return_type begin() const noexcept override
{
return this->operator[](level_index - 1);
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
return_type end() const noexcept override
{
return this->operator[](level_index - 1) + get_nodes_at_level(level_index - 1);
}
private:
static constexpr auto clz = utils::countl_zero<std::size_t>{};
std::size_t pivot;
unique_ptr buf;
};
template <typename DpfKey,
typename Allocator = aligned_allocator<
typename interval_memoizer_key_t<DpfKey>::interior_node>>
struct full_tree_interval_memoizer final : public interval_memoizer_base<DpfKey>
{
private:
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using parent = interval_memoizer_base<DpfKey>;
HEDLEY_PRAGMA(GCC diagnostic pop)
public:
using node_type = typename interval_memoizer_key_t<DpfKey>::interior_node;
using unique_ptr = typename Allocator::unique_ptr;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using return_type = std::add_pointer_t<node_type>;
HEDLEY_PRAGMA(GCC diagnostic pop)
using integral_type = typename interval_memoizer_key_t<DpfKey>::integral_type;
using parent::depth;
using parent::level_index;
using parent::get_nodes_at_level;
static constexpr bool retains_all_levels = true;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
explicit full_tree_interval_memoizer(std::size_t output_len,
Allocator alloc = Allocator{})
: parent::interval_memoizer_base(output_len),
level_endpoints{initialize_endpoints(output_len)},
buf{alloc.allocate_unique_ptr(level_endpoints[depth] + output_len)}
{
if (HEDLEY_UNLIKELY(buf == nullptr)) throw std::bad_alloc{};
}
HEDLEY_PRAGMA(GCC diagnostic pop)
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
return_type operator[](std::size_t level) const noexcept override
{
return Allocator::assume_aligned(&buf[level_endpoints[level]]);
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
return_type begin() const noexcept override
{
return this->operator[](level_index - 1);
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
return_type end() const noexcept override
{
return this->operator[](level_index - 1) + get_nodes_at_level(level_index - 1);
}
private:
const std::array<std::size_t, depth+1> level_endpoints;
unique_ptr buf;
// For n nodes on a given level, there are the following cases:
// n odd => (n+1)/2 nodes on previous level
// ex. 5 nodes on current level grouped as
// |..|..|.| or |.|..|..|
// where both give 3 nodes on previous level
// n even => n/2 OR (n+2)/2 nodes on previous level
// ex. 6 nodes on current level grouped as
// |..|..|..| or |.|..|..|.|
// gives either 3 or 4 nodes on previous level
// Clearly (n+2)/2 is the worst case, so this is used in the derivation
// for the number of nodes on each level.
// Also note that at depth (from the root) i, there can't be more than 2^i
// nodes hence the `min()` function call.
static constexpr auto initialize_endpoints(integral_type len)
{
std::array<std::size_t, depth+1> level_endpoints{0};
for (std::size_t level=depth; level > 0; --level)
{
len = std::min(len+2 >> 1, integral_type(1) << level-1);
level_endpoints[level] = len;
}
for (std::size_t level = 0; level < depth; ++level)
{
level_endpoints[level+1] = level_endpoints[level] + level_endpoints[level+1];
}
return level_endpoints;
}
};
/// Interval memoizer whose leaf depth is `StopLevel` (incremental `eval_interval`).
template <typename DpfKey, std::size_t StopLevel,
typename Allocator = aligned_allocator<
typename interval_memoizer_key_t<DpfKey>::interior_node>>
struct basic_interval_memoizer_at
{
public:
using dpf_type = interval_memoizer_key_t<DpfKey>;
using integral_type = typename dpf_type::integral_type;
using node_type = typename dpf_type::interior_node;
using return_type = node_type *;
using unique_ptr = typename Allocator::unique_ptr;
static constexpr std::size_t depth = StopLevel;
explicit basic_interval_memoizer_at(std::size_t output_len,
Allocator alloc = Allocator{})
: output_length{output_len},
level_index{0},
pivot{std::max((interval_memoizer_slots(output_len) >> 1)
+ (interval_memoizer_slots(output_len) & 1) - 1,
(interval_memoizer_slots(output_len) + 6) >> 2)},
buf{alloc.allocate_unique_ptr(
pivot + ((interval_memoizer_slots(output_len) + 2) >> 1))},
from_{std::nullopt},
to_{std::nullopt}
{
if (HEDLEY_UNLIKELY(buf == nullptr)) throw std::bad_alloc{};
}
std::size_t assign_interval(const dpf_type & dpf, integral_type new_from,
integral_type new_to)
{
static constexpr auto complement_of = std::bit_not{};
if (from_.has_value() == false
|| std::memcmp(&dpf_root_, &dpf.root(), sizeof(node_type)) != 0
|| std::memcmp(&dpf_common_part_hash_, &dpf.common_part_hash(),
sizeof(digest_type)) != 0
|| from_.value_or(complement_of(new_from)) != new_from
|| to_.value_or(complement_of(new_to)) != new_to)
{
if (new_to - new_from > output_length)
throw std::length_error("size of new interval is too large for memoizer");
(*this)[0][0] = dpf.root();
dpf_root_ = dpf.root();
dpf_common_part_hash_ = dpf.common_part_hash();
from_ = new_from;
to_ = new_to;
level_index = 1;
}
return level_index;
}
std::size_t advance_level() { return ++level_index; }
std::size_t get_nodes_at_level() const
{
return get_nodes_at_level(level_index, from_.value_or(0), to_.value_or(0));
}
std::size_t get_nodes_at_level(std::size_t level) const
{
return get_nodes_at_level(level, from_.value_or(0), to_.value_or(0));
}
static std::size_t get_nodes_at_level(std::size_t level, integral_type from_node,
integral_type to_node)
{
std::size_t offset = depth - level;
return utils::shift_right(to_node - integral_type{1}, offset)
- utils::shift_right(from_node, offset) + 1;
}
return_type operator[](std::size_t level) const noexcept
{
bool b = (depth ^ level) & 1;
return Allocator::assume_aligned(&buf[b * pivot]);
}
private:
std::size_t output_length;
std::size_t level_index;
std::size_t pivot;
unique_ptr buf;
node_type dpf_root_;
digest_type dpf_common_part_hash_;
std::optional<integral_type> from_;
std::optional<integral_type> to_;
};
namespace detail
{
template <typename DpfKey,
typename MemoizerT,
typename InputT>
HEDLEY_ALWAYS_INLINE
auto make_interval_memoizer(InputT from, InputT to)
{
using dpf_type = DpfKey;
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
std::size_t nodes_in_interval = utils::get_leafnodes_in_output_interval<dpf_type>(from, to);
return MemoizerT(nodes_in_interval);
}
} // namespace detail
template <typename DpfKey,
typename InputT>
inline auto make_basic_interval_memoizer(InputT from, InputT to)
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using key_t = interval_memoizer_key_t<DpfKey>;
return detail::make_interval_memoizer<key_t, basic_interval_memoizer<key_t>, InputT>(from, to);
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <typename DpfKey,
typename InputT>
inline auto make_basic_interval_memoizer(const DpfKey &, InputT from, InputT to)
{
return make_basic_interval_memoizer<DpfKey>(from, to);
}
template <typename DpfKey>
inline auto make_basic_full_memoizer()
{
using input_type = typename DpfKey::input_type;
return make_basic_interval_memoizer<DpfKey>(
std::numeric_limits<input_type>::min(),
std::numeric_limits<input_type>::max());
}
template <typename DpfKey>
inline auto make_basic_full_memoizer(const DpfKey &)
{
return make_basic_full_memoizer<DpfKey>();
}
template <typename DpfKey,
typename InputT>
inline auto make_full_tree_interval_memoizer(InputT from, InputT to)
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using key_t = interval_memoizer_key_t<DpfKey>;
return detail::make_interval_memoizer<key_t, full_tree_interval_memoizer<key_t>, InputT>(from, to);
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <typename DpfKey,
typename InputT>
inline auto make_full_tree_interval_memoizer(const DpfKey &, InputT from, InputT to)
{
return make_full_tree_interval_memoizer<DpfKey>(from, to);
}
template <typename DpfKey>
inline auto make_full_tree_full_memoizer()
{
using input_type = typename DpfKey::input_type;
return make_full_tree_interval_memoizer<DpfKey>(
std::numeric_limits<input_type>::min(),
std::numeric_limits<input_type>::max());
}
template <typename DpfKey>
inline auto make_full_tree_full_memoizer(const DpfKey &)
{
return make_full_tree_full_memoizer<DpfKey>();
}
template <typename DpfKey, std::size_t StopLevel>
inline auto make_basic_interval_memoizer_at(std::size_t leaf_nodes)
{
return basic_interval_memoizer_at<DpfKey, StopLevel>(leaf_nodes);
}
/// Stop-level interval memoizer for output slot `I` of a multi-level key.
/// Sizes the ping-pong buffer for the lane-domain interval `[from, to]`
/// expanded to `meta[I].tree_level` (the leaf level of slot `I`). This is the
/// default memoizer for a multi-level `eval_interval(out<I>, ...)`.
template <typename DpfKey, std::size_t I,
typename InputT,
std::enable_if_t<DpfKey::is_multilevel, bool> = true>
inline auto make_basic_interval_memoizer(InputT from, InputT to)
{
constexpr std::size_t stop = DpfKey::meta[I].tree_level;
constexpr auto lg = DpfKey::template lg_outputs_per_leaf_of<I>;
using integral_type = typename DpfKey::integral_type;
constexpr auto to_int = utils::to_integral_type<InputT>{};
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
const integral_type from_node = utils::leaf_node_floor(
static_cast<integral_type>(to_int(from)), lg);
const integral_type to_node = utils::leaf_node_ceil_exclusive(
static_cast<integral_type>(to_int(to)), lg);
const auto segs = utils::split_leaf_nodes(from_node, to_node, stop);
return basic_interval_memoizer_at<DpfKey, stop>(segs.total);
}
template <typename DpfKey, std::size_t I,
typename InputT,
std::enable_if_t<DpfKey::is_multilevel, bool> = true>
inline auto make_basic_interval_memoizer(const DpfKey &, InputT from, InputT to)
{
return make_basic_interval_memoizer<DpfKey, I>(from, to);
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_INTERVAL_MEMOIZER_HPP__

253
include/dpf/json.hpp Normal file
View file

@ -0,0 +1,253 @@
/// @file dpf/json.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_JSON_HPP__
#define LIBDPF_INCLUDE_DPF_JSON_HPP__
#include <cstddef>
#include <cstdint>
#include <tuple>
#include <array>
#include <string>
#include <bitset>
#include <type_traits>
#include <utility>
#include "json/include/nlohmann/json.hpp"
#include "portable-snippets/exact-int/exact-int.h"
#include "dpf/dpf_key.hpp"
namespace nlohmann
{
template <typename NodeT,
typename OutputT>
struct adl_serializer<dpf::beaver<true, NodeT, OutputT>>
{
static void from_json(const nlohmann::json & j, dpf::beaver<true, NodeT, OutputT> & beaver) // NOLINT(runtime/references)
{
j.get_to(beaver.output_blind);
j.get_to(beaver.vector_blind);
j.get_to(beaver.blinded_vector);
}
static void to_json(nlohmann::json & j, const dpf::beaver<true, NodeT, OutputT> & beaver) // NOLINT(runtime/references)
{
j = nlohmann::json{
{"output_blind", beaver.output_blind},
{"vector_blind", beaver.vector_blind},
{"blinded_vector", beaver.blinded_vector}
};
}
};
template <>
struct adl_serializer<simde__m128i>
{
static void from_json(const nlohmann::json & j, simde__m128i & a) // NOLINT(runtime/references)
{
std::array<psnip_uint64_t, 2> A;
j.get_to(A);
a = simde_mm_set_epi64x(A[1], A[0]);
}
static void to_json(nlohmann::json & j, const simde__m128i & a) // NOLINT(runtime/references)
{
j = nlohmann::json{a[0], a[1]};
}
};
template <>
struct adl_serializer<simde__m256i>
{
static void from_json(const nlohmann::json & j, simde__m256i & a) // NOLINT(runtime/references)
{
std::array<psnip_uint64_t, 4> A;
j.get_to(A);
a = simde_mm256_set_epi64x(A[3], A[2], A[1], A[0]);
}
static void to_json(nlohmann::json & j, const simde__m256i & a) // NOLINT(runtime/references)
{
j = nlohmann::json{a[0], a[1], a[2], a[3]};
}
};
template <>
struct adl_serializer<dpf::detail::cmp_meta>
{
static void from_json(const nlohmann::json & j, dpf::detail::cmp_meta & c) // NOLINT(runtime/references)
{
j.at("nbits").get_to(c.nbits);
j.at("mask").get_to(c.mask);
c.kind = static_cast<dpf::cmp_kind>(j.at("kind").get<psnip_uint8_t>());
c.trivial =
static_cast<dpf::cmp_trivial>(j.at("trivial").get<psnip_uint8_t>());
j.at("eval_as_ge").get_to(c.eval_as_ge);
j.at("include_eq").get_to(c.include_eq);
j.at("active").get_to(c.active);
}
static void to_json(nlohmann::json & j, const dpf::detail::cmp_meta & c) // NOLINT(runtime/references)
{
j = nlohmann::json{
{"nbits", c.nbits},
{"mask", c.mask},
{"kind", static_cast<psnip_uint8_t>(c.kind)},
{"trivial", static_cast<psnip_uint8_t>(c.trivial)},
{"eval_as_ge", c.eval_as_ge},
{"include_eq", c.include_eq},
{"active", c.active}
};
}
};
// Classic single-level key (no `at<>` / no comparison channel).
template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs>
struct adl_serializer<dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>,
std::enable_if_t<!dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, OutputT,
OutputTs...>::is_multilevel>>
{
using dpf_type = dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>;
using interior_node = typename dpf_type::interior_node;
using leaf_tuple = typename dpf_type::leaf_tuple;
using beaver_tuple = typename dpf_type::beaver_tuple;
static dpf_type from_json(const nlohmann::json & j)
{
interior_node root;
j.at("root").get_to(root);
std::array<interior_node, dpf_type::depth> correction_words;
j.at("correction_words").get_to(correction_words);
std::array<psnip_uint8_t, dpf_type::depth> correction_advice;
j.at("correction_advice").get_to(correction_advice);
leaf_tuple leaves;
j.at("leaves").get_to(leaves);
std::string wildcard_mask_str;
j.at("wildcards").get_to(wildcard_mask_str);
beaver_tuple beavers;
j.at("beavers").get_to(beavers);
return dpf_type{
root,
correction_words,
correction_advice,
leaves,
std::bitset<std::tuple_size_v<leaf_tuple>>(wildcard_mask_str),
beavers
};
}
static void to_json(nlohmann::json & j, const dpf_type & dpf) // NOLINT(runtime/references)
{
j = nlohmann::json{
{"root", dpf.root()},
{"correction_words", dpf.correction_words()},
{"correction_advice", dpf.correction_advice()},
{"leaves", dpf.mutable_leaf_tuple()},
{"wildcards", dpf.mutable_wildcard_mask()},
{"beavers", dpf.mutable_beaver_tuple()}
};
}
};
// Multi-level / comparison key (`at<>` and/or a `cmp` channel). Round-trips
// the public tree (root, CWs, advice) and the comparison channel (cmp meta,
// value CWs, `cw_last`, and this party's `cmp_addend` share). Leaf outputs are
// not yet serialized here, so this path currently supports comparison-only
// keys (`num_outputs == 0`, e.g. `make_dpf(x, dpf::lt(...))`).
template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs>
struct adl_serializer<dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>,
std::enable_if_t<dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, OutputT,
OutputTs...>::is_multilevel>>
{
using dpf_type = dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>;
using interior_node = typename dpf_type::interior_node;
using input_type = typename dpf_type::input_type;
static dpf_type from_json(const nlohmann::json & j)
{
static_assert(dpf_type::num_outputs == 0,
"dpf::json round-trip currently supports comparison-only "
"multi-level keys (no leaf outputs)");
interior_node root;
j.at("root").get_to(root);
typename dpf_type::correction_words_array correction_words;
j.at("correction_words").get_to(correction_words);
typename dpf_type::correction_advice_array correction_advice;
j.at("correction_advice").get_to(correction_advice);
dpf::detail::cmp_meta cmp;
j.at("cmp").get_to(cmp);
typename dpf_type::value_cw_array value_cws;
j.at("value_cw").get_to(value_cws);
uint64_t cw_last = j.at("cw_last").template get<uint64_t>();
uint64_t cmp_addend = j.at("cmp_addend").template get<uint64_t>();
typename dpf_type::leaf_wrapper_tuple leaves{};
input_type offset_share{};
typename dpf_type::addend_tuple addends{};
return dpf_type{root, correction_words, correction_advice,
std::move(leaves), offset_share, cmp, value_cws, cw_last,
cmp_addend, addends};
}
static void to_json(nlohmann::json & j, const dpf_type & dpf) // NOLINT(runtime/references)
{
static_assert(dpf_type::num_outputs == 0,
"dpf::json round-trip currently supports comparison-only "
"multi-level keys (no leaf outputs)");
j = nlohmann::json{
{"root", dpf.root()},
{"correction_words", dpf.correction_words()},
{"correction_advice", dpf.correction_advice()},
{"cmp", dpf.cmp()},
{"value_cw", dpf.value_cw()},
{"cw_last", static_cast<uint64_t>(dpf.cw_last())},
{"cmp_addend", static_cast<uint64_t>(dpf.cmp_addend())}
};
}
};
} // namespace nlohmann
namespace dpf
{
namespace json
{
template <typename DpfKey>
static std::string to_json(const DpfKey & dpf)
{
nlohmann::json json = dpf;
return json.dump();
}
template <typename DpfType>
static auto from_json(const std::string & json_string)
{
nlohmann::json json = nlohmann::json::parse(json_string);
return static_cast<DpfType>(json);
}
} // namespace json
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_JSON_HPP__

546
include/dpf/keyword.hpp Normal file
View file

@ -0,0 +1,546 @@
/// @file dpf/keyword.hpp
/// @brief defines `dpf::keyword` and associated helpers
/// @details A `dpf::keyword` is an integer representation of a fixed-length
/// string over a given alphabet. The integer representation uses
/// the fewest bits possible for the given string length and alphabet
/// size and uses an encoding that preserves the lexicographic
/// ordering of the underlying strings. This type is inteded to be
/// used as an input type for a DPF and, as such, specializes
/// `dpf::utils::bitlength_of`, `dpf::utils::msb_of`, and
/// `dpf::utils::countl_zero_symmetric_difference`. When used as a
/// DPF input type, the aforementioned properties of the encoding
/// equate to minimizing the DPF tree depth and maximizing the
/// potential for effective memoization in the context of
/// `eval_point`- and `eval_sequence`-based evaluation. As a discreet
/// (non-numeric) type, `dpf::keyword`s are not optimized for use in
/// `eval_interval`-based evaluation.
///
/// This file also defines the `dpf::alphabets` namespace, which
/// defines various alphabets of interest, including the printable
/// ASCII characters (`dpf::alphabets::printable_ascii`), lowercase
/// Roman letters (`dpf::alphabets::lowercase_alpha`), lowercase
/// hexademical digits (`dpf::alpbabets::lowercase_hex`), among
/// others.
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_KEYWORD_HPP__
#define LIBDPF_INCLUDE_DPF_KEYWORD_HPP__
#include <cstddef>
#include <cmath>
#include <type_traits>
#include <limits>
#include <string>
#include <string_view>
#include <memory>
#include <iterator>
#include <algorithm>
#include <array>
#include <istream>
#include <ostream>
#include <stdexcept>
#include "hedley/hedley.h"
#include "dpf/utils.hpp"
#include "dpf/modint.hpp"
namespace dpf
{
/// @brief defines common alphabets for convenient use with `dpf::keyword`
/// N.B.: The first char in an alphabet has value `0`. All strings will be
/// implicitly padded to the max length by prepending this char. For
/// strings, it should typically be `\0`; for numbers, the zero digit.
namespace alphabets
{
/// @brief the printable ASCII chars
inline constexpr char printable_ascii[] = "\0 !\"#$%&'()*+,-./0123456789:"
";<=>?@ABCDEFGHIJKLMNOPQRSTUV"
"WXYZ[\\]^_`abcdefghijklmnopq"
"rstuvwxyz{|}~";
/// @brief the extended ASCII characters (0-255) using hexadecimal escape sequences with lowercase letters
inline constexpr char extended_ascii[] =
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~\x7f"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
/// @brief the lowercase Roman alphabet
inline constexpr char lowercase_alpha[] = "\0abcdefghijklmnopqrstuvwxyz";
/// @brief the lowercase and uppercase Roman alphabet
inline constexpr char alpha[] = "\0abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL"
"MNOPQRSTUVWXYZ";
/// @brief the lowercase and uppercase Roman alphabet plus digits 0-9
inline constexpr char alphanumeric[] = "\0abcdefghijklmnopqrstuvwxyzABCDE"
"FGHIJKLMNOPQRSTUVWXYZ0123456789";
/// @brief the lowercase Roman alphabet plus digits 0-9
inline constexpr char lowercase_alphanumeric[] = "\0abcdefghijklmnopqrstu"
"vwxyz0123456789";
/// @brief hashtags
inline constexpr char hashtag[] = "\0abcdefghijklmnopqrstuvwxyz#-";
/// @brief binary
inline constexpr char binary[] = "01";
/// @brief octal
inline constexpr char octal[] = "01234567";
/// @brief decimal
inline constexpr char decimal[] = "0123456789";
/// @brief hex w/ lowercase letters
inline constexpr char hex[] = "0123456789abcdef";
/// @brief hex w/ uppercase letters
inline constexpr char uppercase_hex[] = "0123456789ABCDEF";
/// @brief base64 digits
inline constexpr char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"
"lmnopqrstuvwxyz0123456789+/=";
/// @brief URL-safe base64 digits
inline constexpr char url64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"
"lmnopqrstuvwxyz0123456789-_";
/// @brief URI alphabet
inline constexpr char uri[] = "\0:/?#[]@" // gen-delims
"!$&'()*+,;=" // sub-delims
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789-._~%";
/// @brief lowercase email address
inline constexpr char email[] = "\0abcdefghijklmnopqrstuvwxyz"
"0123456789.-_@";
/// @brief Distinct UTF-8 bytes of a small emoji sample.
/// A `char` alphabet cannot store one symbol per emoji: those
/// code points are several bytes long, and repeated bytes would
/// make `find` non-injective. This list keeps first-seen bytes only.
inline constexpr char emoji[] =
"\x00\xf0\x9f\x98\x80\x81\x82\xa4\xa3\x83\x84\x85\x86\x89\x8a\x8b"
"\x8e\x8d\xa5\xb0\x8f";
} // namespace alphabets
template <std::size_t MaxLen,
// std::size_t MinLen = 1,
typename CharT = char,
const CharT * Alphabet = alphabets::printable_ascii,
typename Traits = std::char_traits<CharT>,
typename Allocator = std::allocator<CharT>>
class basic_fixed_length_string : public dpf::modint<static_cast<std::size_t>(std::ceil(MaxLen*std::log2(std::basic_string_view<CharT, Traits>(&Alphabet[1]).size() + 1)))>
{
public:
using string_view = std::basic_string_view<CharT, Traits>;
/// @brief radix used by the integer representation of the string
static constexpr std::size_t radix = string_view(&Alphabet[1]).size() + 1;
/// @brief the alphabet over which the string is constructed
static constexpr string_view alphabet = string_view(Alphabet, radix);
// /// @brief the minimum explicit length of a string
// /// @details strings of length at least `min_length` are padded to
// /// `max_length` with the 0th character in the alphabet; strings
// /// of length less than `min_length` are out of range
// static constexpr std::size_t min_length = MinLen;
/// @brief the (maximum) length of a string
static constexpr std::size_t max_length = MaxLen;
/// @brief the number of bits needed to uniquely represent any string
/// of length at least `min_length` and at most `max_length` over
/// `alphabet`
static constexpr std::size_t bits
= std::ceil((max_length)*std::log2(radix));
static_assert(!alphabet.empty(), "alphabet must be non-empty");
// static_assert(MinLen != 0, "minimum string length must be positive");
static_assert(MaxLen != 0, "maximum string length must be positive");
// static_assert(MinLen <= MaxLen, "minimum string length must be less than or equal to maximum");
private:
using parent = dpf::modint<bits>;
public:
/// @brief the primitive integral type used to represent the string
using integral_type = dpf::utils::nonvoid_integral_type_from_bitlength_t<bits>;
/// @brief construct the `basic_fixed_length_string`
/// @{
/// @brief default constructor
/// @details Constructs the `basic_fixed_length_string` with a value
/// corresponding to the empty string.
constexpr basic_fixed_length_string() noexcept = default;
/// @brief copy constructor
/// @details Constructs the `basic_fixed_length_string` with a value
/// copied from another `basic_fixed_length_string`.
constexpr
basic_fixed_length_string(const basic_fixed_length_string &)
noexcept = default;
/// @brief move constructor
/// @details Constructs the `basic_fixed_length_string` from another
/// `basic_fixed_length_string` using move semantics.
constexpr
basic_fixed_length_string(basic_fixed_length_string &&)
noexcept = default;
/// @brief value constructor
/// @details Constructs a `basic_fixed_length_string` whose value is
/// initialized to the integer representation of `str`.
/// @param str the string to initialize with
constexpr
// cppcheck-suppress noExplicitConstructor
basic_fixed_length_string(string_view str) // NOLINT(runtime/explicit)
: parent::modint(encode_(str)) { }
/// @brief value constructor
/// @details Constructs a `basic_fixed_length_string` whose value is
/// initialized to the integer representation of `str`.
/// @param str the string to initialize with
constexpr
// cppcheck-suppress noExplicitConstructor
basic_fixed_length_string(const CharT * str) // NOLINT(runtime/explicit)
: parent::modint(encode_(str)) { }
/// @}
/// @brief assign the `basic_fixed_length_string`
/// @{
/// @brief value assignment
/// @details Sets the value of this `basic_fixed_length_string` to
/// the integer representation of `str`.
/// @param str the string to assign with
constexpr basic_fixed_length_string & operator=(string_view str)
{
parent::operator=(encode_(str));
return *this;
}
/// @brief copy assignment
/// @details Assigns the `basic_fixed_length_string` with a value
/// copied from another `basic_fixed_length_string`.
constexpr basic_fixed_length_string &
operator=(const basic_fixed_length_string &) = default;
/// @brief move assignment
/// @details Assigns the `basic_fixed_length_string` from another
/// `basic_fixed_length_string` using move semantics.
constexpr basic_fixed_length_string &
operator=(basic_fixed_length_string &&) noexcept = default;
/// @}
~basic_fixed_length_string() = default;
HEDLEY_PURE
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr basic_fixed_length_string operator~() const noexcept
{
return basic_fixed_length_string{parent::operator~()};
}
/// @brief recreates the string representation of this
/// `basic_fixed_length_string`
/// @complexity `O(MaxLen)` where `MaxLen` is the maximum string length
constexpr
operator std::basic_string<CharT, Traits, Allocator>() const
{
auto tmp = parent::reduced_value();
std::basic_string<CharT, Traits, Allocator> rev;
rev.reserve(max_length);
while (tmp != 0)
{
rev.push_back(alphabet[static_cast<std::size_t>(tmp % radix)]);
tmp /= radix;
}
std::reverse(std::begin(rev), std::end(rev));
return rev;
}
private:
constexpr
// cppcheck-suppress noExplicitConstructor
basic_fixed_length_string(integral_type val) // NOLINT(runtime/explicit)
noexcept
: parent::modint(val) { }
constexpr basic_fixed_length_string(parent val) noexcept
: parent::modint(val) { }
/// @brief converts a string of length at-most `max_length` over
/// `alphabet` into an integer
/// @throws `std::length_error` if `str` exceeds `max_length`
/// @throws `std::domain_error` if `str` contains a char not in `alphabet`
HEDLEY_ALWAYS_INLINE
static constexpr integral_type encode_(string_view str)
{
constexpr auto npos = string_view::npos;
using std::string_literals::operator""s;
utils::constexpr_maybe_throw<std::length_error>(
str.size() > max_length,
"str.size() cannot exceed max_length");
integral_type val{0};
for (CharT c : str)
{
auto next_digit = digit_of_(c);
utils::constexpr_maybe_throw<std::domain_error>(
next_digit == npos,
"str contains a disallowed char");
val = val * radix + next_digit;
}
return val;
}
/// @brief Index of `c` in `alphabet`, or `npos` when `c` is absent.
/// Byte alphabets use a 256-entry table; wider character types scan.
static constexpr std::size_t digit_of_(CharT c)
{
constexpr auto missing = string_view::npos;
if constexpr (sizeof(CharT) == 1)
{
constexpr auto table = []()
{
std::array<std::size_t, 256> digits{};
for (auto & slot : digits)
slot = missing;
for (std::size_t i = 0; i < radix; ++i)
{
auto uc = static_cast<unsigned char>(alphabet[i]);
if (digits[uc] == missing)
digits[uc] = i;
}
return digits;
}();
return table[static_cast<unsigned char>(c)];
}
else
{
return alphabet.find(c);
}
}
/// @brief performs stream input and output on
/// `dpf::basic_fixed_length_string`s
/// @{
/// @brief Writes the decoded string, not the packed integer.
friend std::basic_ostream<CharT, Traits> &
operator<<(std::basic_ostream<CharT, Traits> & os,
const basic_fixed_length_string & k)
{
return os << static_cast<std::basic_string<CharT, Traits, Allocator>>(k);
}
/// @brief Reads a whitespace-delimited token and encodes it.
friend std::basic_istream<CharT, Traits> &
operator>>(std::basic_istream<CharT, Traits> & is,
basic_fixed_length_string & k)
{
std::basic_string<CharT, Traits, Allocator> tmp;
if (!(is >> tmp))
return is;
try
{
k = basic_fixed_length_string(string_view(tmp));
}
catch (const std::exception &)
{
is.setstate(std::ios::failbit);
}
return is;
}
/// @}
friend struct utils::countl_zero_symmetric_difference<basic_fixed_length_string>;
friend struct utils::msb_of<basic_fixed_length_string>;
friend struct utils::mod_pow_2<basic_fixed_length_string>;
friend struct utils::make_from_integral_value<basic_fixed_length_string>;
}; // class dpf::basic_fixed_length_string
/// @brief instantiation of the `dpf::basic_fixed_length_string` class that
/// uses `char` (i.e., bytes) as its **character type**, with its
/// default `char_traits` and `allocator` types (see
/// `dpf::basic_fixed_length_string` for more info on the template).
template <std::size_t MaxLen,
const char * Alphabet = alphabets::lowercase_alpha>
using keyword = basic_fixed_length_string<MaxLen, char, Alphabet>;
/// @brief convert a `dpf::basic_fixed_length_string` to a `std::basic_string`
/// @details Uses a `static_cast` to convert `str` to recreate the string
/// representation of a `basic_fixed_length_string`
/// @complexity `O(MaxLen)` where `MaxLen` is the maximum string length
template <std::size_t MaxLen,
typename CharT,
const CharT * Alphabet,
typename Traits = std::char_traits<CharT>,
typename Allocator = std::allocator<CharT>>
static constexpr std::basic_string<CharT, Traits, Allocator>
to_string(basic_fixed_length_string<MaxLen, CharT, Alphabet, Traits, Allocator>
str)
{
return static_cast<std::basic_string<CharT, Traits, Allocator>>(str);
}
namespace utils
{
/// @brief specializes `dpf::bitlength_of` for `dpf::basic_fixed_length_string`
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
struct bitlength_of<
dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>>
: public std::integral_constant<std::size_t,
dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>::bits> { };
/// @brief specializes `dpf::msb_of` for `dpf::basic_fixed_length_string`
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
struct msb_of<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>>
{
using T = dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>;
using U = typename T::integral_type;
static constexpr T value = U{1} << bitlength_of_v<T> - 1ul;
};
/// @brief specializes `dpf::countl_zero_symmetric_difference` for
/// `dpf::basic_fixed_length_string`
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
struct countl_zero_symmetric_difference<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>>
: countl_zero_symmetric_difference<typename dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>::parent>
{ };
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
struct mod_pow_2<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>>
: mod_pow_2<typename dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>::parent>
{ };
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
struct make_from_integral_value<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>>
{
using T = dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>;
using integral_type = integral_type_from_bitlength_t<bitlength_of_v<T>>;
constexpr T operator()(integral_type val) const noexcept
{
return T{val};
}
};
} // namespace utils
} // namespace dpf
namespace std
{
/// @brief specializes `std::numeric_limits` for CV-qualified `dpf::keyword`s
/// @{
/// @details specializes `std::numeric_limits` for `dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>`
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
class numeric_limits<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>>
{
public:
using keyword_type = dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>;
static constexpr bool is_specialized = true;
static constexpr bool is_signed = false;
static constexpr bool is_integer = true;
static constexpr bool is_exact = true;
static constexpr bool has_infinity = false;
static constexpr bool has_quiet_NaN = false;
static constexpr bool has_signaling_NaN = false;
static constexpr std::float_denorm_style has_denorm = std::denorm_absent;
static constexpr bool has_denorm_loss = false;
static constexpr std::float_round_style round_style = std::round_toward_zero;
static constexpr bool is_iec559 = false;
static constexpr bool is_bounded = true;
static constexpr bool is_modulo = true;
static constexpr int digits = keyword_type::bits;
static constexpr int digits10 = static_cast<int>((static_cast<unsigned long long>(keyword_type::bits) * 30103ull) / 100000ull);
static constexpr int max_digits10 = 0;
static constexpr int radix = 2;
static constexpr int min_exponent = 0;
static constexpr int max_exponent = 0;
static constexpr int min_exponent10 = 0;
static constexpr int max_exponent10 = 0;
static constexpr bool traps
= std::numeric_limits<typename keyword_type::integral_type>::traps;
static constexpr bool tinyness_before = false;
static constexpr keyword_type min() noexcept { return keyword_type{""}; }
static constexpr keyword_type lowest() noexcept { return keyword_type{""}; }
static constexpr keyword_type max() noexcept { return ~keyword_type{""}; }
static constexpr keyword_type epsilon() noexcept { return 0; }
static constexpr keyword_type round_error() noexcept { return 0; }
static constexpr keyword_type infinity() noexcept { return 0; }
static constexpr keyword_type quiet_NaN() noexcept { return 0; }
static constexpr keyword_type signaling_NaN() noexcept { return 0; }
static constexpr keyword_type denorm_min() noexcept { return 0; }
};
/// @details specializes `std::numeric_limits` for `dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc> const`
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
class numeric_limits<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc> const>
: public numeric_limits<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>> {};
/// @details specializes `std::numeric_limits` for
/// `dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc> volatile`
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
class numeric_limits<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc> volatile>
: public numeric_limits<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>> {};
/// @details specializes `std::numeric_limits` for
/// `dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc> const volatile`
template <std::size_t MaxLen,
typename CharT,
const CharT * Alpha,
typename Traits,
typename Alloc>
class numeric_limits<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc> const volatile>
: public numeric_limits<dpf::basic_fixed_length_string<MaxLen, CharT, Alpha, Traits, Alloc>> {};
/// @}
} // namespace std
#endif // LIBDPF_INCLUDE_DPF_KEYWORD_HPP__

1914
include/dpf/keyword2.hpp Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

485
include/dpf/leaf_node.hpp Normal file
View file

@ -0,0 +1,485 @@
/// @file dpf/leaf_node.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @author Christopher Jiang <christopher.jiang@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_LEAF_NODE_HPP__
#define LIBDPF_INCLUDE_DPF_LEAF_NODE_HPP__
#include "hedley/hedley.h"
#include <cstddef>
#include <cmath>
#include <cstring>
#include <type_traits>
#include <utility>
#include <memory>
#include <functional>
#include <tuple>
#include <atomic>
#include <array>
#include "simde/simde/x86/avx2.h"
#include "dpf/bit.hpp"
#include "dpf/packed_lane.hpp"
#include "dpf/xor_wrapper.hpp"
#include "dpf/wildcard.hpp"
#include "dpf/leaf_arithmetic.hpp"
#include "dpf/utils.hpp"
#include "dpf/random.hpp"
namespace dpf
{
/// @brief `value` is `true` if multiple leaves are packed into each leaf node
template <typename OutputT,
typename NodeT>
using is_packable = std::bool_constant<
std::less<>{}(utils::bitlength_of_output_v<OutputT, NodeT>, utils::bitlength_of_output_v<NodeT, NodeT>) &&
std::equal_to<>{}(utils::bitlength_of_output_v<NodeT, NodeT> % utils::bitlength_of_output_v<OutputT, NodeT>, 0)>;
template <typename OutputT,
typename NodeT>
static constexpr bool is_packable_v = is_packable<OutputT, NodeT>::value;
template <typename OutputT,
typename NodeT>
struct outputs_per_leaf
: public std::integral_constant<std::size_t,
!is_packable_v<OutputT, NodeT> ? 1 :
utils::bitlength_of_output_v<NodeT, NodeT> / utils::bitlength_of_output_v<OutputT, NodeT>> { };
template <typename OutputT,
typename NodeT>
static constexpr std::size_t outputs_per_leaf_v
= outputs_per_leaf<OutputT, NodeT>::value;
template <typename OutputT,
typename NodeT>
static constexpr std::size_t lg_outputs_per_leaf_v
= std::log2(outputs_per_leaf<OutputT, NodeT>::value);
template <typename OutputT,
typename NodeT>
struct block_length_of_leaf
: std::integral_constant<std::size_t, is_packable_v<OutputT, NodeT> ? 1 :
utils::quotient_ceiling(
utils::bitlength_of_output_v<OutputT, NodeT>,
utils::bitlength_of_output_v<NodeT, NodeT>)
>{ };
template <typename OutputT,
typename NodeT>
static constexpr std::size_t block_length_of_leaf_v
= block_length_of_leaf<OutputT, NodeT>::value;
template <typename OutputT,
typename NodeT,
typename InputT>
constexpr std::size_t offset_within_block(InputT x) noexcept
{
constexpr auto mod = utils::mod_pow_2<InputT>{};
return mod(x, dpf::lg_outputs_per_leaf_v<OutputT, NodeT>);
}
template <std::size_t I,
typename N,
std::size_t I_,
typename OutputsT>
struct block_offset_of_leaf
{
static constexpr std::size_t value = dpf::block_length_of_leaf_v<std::tuple_element_t<I_, OutputsT>, N>
+ block_offset_of_leaf<I, N, I_+1, OutputsT>::value;
};
template <std::size_t I,
typename N,
typename OutputsT>
struct block_offset_of_leaf<I, N, I, OutputsT>
{
static constexpr std::size_t value = 0;
};
template <std::size_t I, typename N, typename OutputsT>
inline constexpr std::size_t block_offset_of_leaf_v
= block_offset_of_leaf<I, N, 0, OutputsT>::value;
template <std::size_t First, std::size_t ...Rest>
struct const_min_size
{
static constexpr std::size_t value
= (First < const_min_size<Rest...>::value)
? First : const_min_size<Rest...>::value;
};
template <std::size_t Only>
struct const_min_size<Only>
{
static constexpr std::size_t value = Only;
};
template <std::size_t First, std::size_t ...Rest>
struct const_max_size
{
static constexpr std::size_t value
= (First > const_max_size<Rest...>::value)
? First : const_max_size<Rest...>::value;
};
template <std::size_t Only>
struct const_max_size<Only>
{
static constexpr std::size_t value = Only;
};
/// PRG position span covering output indices `Is...` of `OutputsTuple`.
/// `is_contiguous` is true when the selected outputs occupy a hole-free
/// range, so one `ExteriorPRG::eval(..., count, pos_min)` produces every
/// leaf mask.
template <typename NodeT,
typename OutputsTuple,
std::size_t ...Is>
struct leaf_prg_range
{
static constexpr std::size_t pos_min
= const_min_size<block_offset_of_leaf_v<Is, NodeT, OutputsTuple>...>::value;
static constexpr std::size_t pos_end
= const_max_size<(block_offset_of_leaf_v<Is, NodeT, OutputsTuple>
+ block_length_of_leaf_v<std::tuple_element_t<Is, OutputsTuple>, NodeT>)...>::value;
static constexpr std::size_t count = pos_end - pos_min;
static constexpr std::size_t needed
= (block_length_of_leaf_v<std::tuple_element_t<Is, OutputsTuple>, NodeT> + ...);
static constexpr bool is_contiguous = (count == needed);
};
template <typename NodeT,
typename OutputT,
std::size_t block_len = block_length_of_leaf_v<OutputT, NodeT>>
struct leaf_node
{
static_assert(block_len == block_length_of_leaf_v<OutputT, NodeT>);
using type = std::array<NodeT, block_len>;
};
template <typename NodeT,
typename OutputT>
struct leaf_node<NodeT, OutputT, 1>
{
static_assert(1 == block_length_of_leaf_v<OutputT, NodeT>);
using type = NodeT;
};
template <typename NodeT,
typename OutputT>
using leaf_node_t = typename leaf_node<NodeT, OutputT>::type;
template <typename NodeT,
typename OutputT,
typename ...OutputTs>
struct leaf_tuple
{
using type = std::tuple<leaf_node_t<NodeT, OutputT>,
leaf_node_t<NodeT, OutputTs>...>;
};
template <typename NodeT,
typename OutputT,
typename ...OutputTs>
using leaf_tuple_t = typename leaf_tuple<NodeT, OutputT, OutputTs...>::type;
template <bool isWildcard,
typename NodeT,
typename OutputT>
struct beaver final { char c = '\0'; };
template <typename NodeT,
typename OutputT>
struct beaver<true, NodeT, OutputT> final
{
using LeafT = dpf::leaf_node_t<NodeT, OutputT>;
OutputT output_blind;
LeafT vector_blind;
LeafT blinded_vector;
};
template <typename NodeT,
typename OutputT,
typename ...OutputTs>
struct beaver_tuple
{
using type = std::tuple<beaver<is_wildcard_v<OutputT>, NodeT, concrete_type_t<OutputT>>,
beaver<is_wildcard_v<OutputTs>, NodeT, concrete_type_t<OutputTs>>...>;
};
template <typename NodeT,
typename OutputT,
typename ...OutputTs>
using beaver_tuple_t = typename beaver_tuple<NodeT, OutputT, OutputTs...>::type;
template <typename NodeT,
typename OutputT>
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static OutputT extract_leaf(const leaf_node_t<NodeT, OutputT> & leaf, std::size_t x) noexcept
{
auto off = offset_within_block<OutputT, NodeT>(x);
OutputT y;
if constexpr (utils::is_packed_subbyte_v<OutputT>)
{
y = packed::extract_lane<OutputT>(leaf, off);
}
else
{
std::memcpy(&y,
reinterpret_cast<const unsigned char *>(std::addressof(leaf))
+ off * sizeof(OutputT),
sizeof(y));
}
return y;
}
// Inserts y at correct place (based on x) within a (otherwise 0) NodeT
template <typename NodeT,
typename InputT,
typename OutputT>
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
auto make_naked_leaf(InputT x, OutputT y) noexcept
{
using leaf_type = dpf::leaf_node_t<NodeT, OutputT>;
auto off = offset_within_block<OutputT, NodeT>(x);
leaf_type Y{};
if constexpr (utils::is_packed_subbyte_v<OutputT>)
{
packed::deposit_lane(Y, off, y);
}
else if constexpr (!dpf::is_wildcard_v<OutputT>)
{
std::memcpy(reinterpret_cast<unsigned char *>(std::addressof(Y))
+ off * sizeof(OutputT),
std::addressof(y), sizeof(OutputT));
}
return Y;
}
/// Address of the first `NodeT` block inside a leaf.
/// A one-block leaf *is* a `NodeT`; a longer leaf is `std::array<NodeT, N>`.
template <typename NodeT, typename LeafT>
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr auto * leaf_blocks(LeafT & leaf) noexcept
{
if constexpr (std::is_same_v<std::remove_cv_t<LeafT>, NodeT>)
return std::addressof(leaf);
else
return leaf.data();
}
template <typename ExteriorPRG,
std::size_t I,
typename OutputsTuple,
typename InteriorBlock>
auto make_leaf_mask_inner(const InteriorBlock & seed, std::size_t pos_base = 0)
{
using node_type = typename ExteriorPRG::block_type;
using output_type = std::tuple_element_t<I, OutputsTuple>;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using leaf_type = dpf::leaf_node_t<node_type, output_type>;
auto count = dpf::block_length_of_leaf_v<output_type, node_type>;
auto pos = pos_base + dpf::block_offset_of_leaf_v<I, node_type, OutputsTuple>;
leaf_type output;
auto seed_ = utils::to_exterior_node<node_type>(seed);
ExteriorPRG::eval(seed_, leaf_blocks<node_type>(output), count,
static_cast<psnip_uint32_t>(pos));
return output;
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <typename ExteriorPRG,
std::size_t I,
typename OutputsTuple,
typename InteriorBlock>
auto make_leaf_mask(const InteriorBlock & seed0, const InteriorBlock & seed1,
std::size_t pos_base = 0)
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using output_type = concrete_type_t<std::tuple_element_t<I, OutputsTuple>>;
auto mask0 = make_leaf_mask_inner<ExteriorPRG, I, OutputsTuple, InteriorBlock>(
seed0, pos_base);
auto mask1 = make_leaf_mask_inner<ExteriorPRG, I, OutputsTuple, InteriorBlock>(
seed1, pos_base);
return dpf::subtract_leaf<output_type>(mask1, mask0);
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <typename ExteriorPRG,
std::size_t I,
typename InputT,
typename ExteriorBlock,
typename ...OutputTs>
auto make_leaf(InputT x, const ExteriorBlock & seed0, const ExteriorBlock & seed1, bool sign,
std::size_t pos_base, OutputTs ...ys)
{
using output_tuple_type = std::tuple<OutputTs...>;
output_tuple_type output_tuple = std::make_tuple(ys...);
using output_type = std::tuple_element_t<I, output_tuple_type>;
output_type Y = std::get<I>(output_tuple);
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using node_type = typename ExteriorPRG::block_type;
return sign ? dpf::subtract_leaf<output_type>(
make_naked_leaf<node_type>(x, Y),
make_leaf_mask<ExteriorPRG, I, output_tuple_type, ExteriorBlock>(
seed0, seed1, pos_base))
: dpf::subtract_leaf<output_type>(
make_leaf_mask<ExteriorPRG, I, output_tuple_type, ExteriorBlock>(
seed0, seed1, pos_base),
make_naked_leaf<node_type>(x, Y));
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <typename ExteriorPRG,
typename InputT,
typename ExteriorBlock,
typename ...OutputTs,
std::size_t ...Is>
auto make_leaves_impl(InputT x, const ExteriorBlock & seed0, const ExteriorBlock & seed1,
bool sign, std::size_t pos_base, std::index_sequence<Is...>, OutputTs ...ys)
{
return std::make_tuple(
make_leaf<ExteriorPRG, Is>(x, seed0, seed1, sign, pos_base, ys...)...);
}
template <typename ExteriorPRG,
typename InputT,
typename ExteriorBlock,
typename OutputT,
typename ...OutputTs,
typename Indices = std::make_index_sequence<1+sizeof...(OutputTs)>>
auto make_leaves(InputT x, const ExteriorBlock & seed0, const ExteriorBlock & seed1,
bool sign, std::size_t pos_base, OutputT y, OutputTs ...ys)
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using node_type = typename ExteriorPRG::block_type;
using leaf_type = dpf::leaf_tuple_t<node_type, OutputT, OutputTs...>;
using beaver_type = dpf::beaver_tuple_t<node_type, OutputT, OutputTs...>;
HEDLEY_PRAGMA(GCC diagnostic pop)
leaf_type leaves = make_leaves_impl<ExteriorPRG>(x, seed0, seed1, sign,
pos_base, Indices{}, y, ys...);
// post-processing to secret-share any wildcard leaves
// that is, after the call to `make_leaves_impl`, any values that were
// should be `wildcards` will currently have a correction_word for `0` in
// `leaves`. Below is a glorified loop that creates two tuples from `leaves`
// (stored in the pair `return_tuple`). For concrete output_types, it simply copies the
// corresponding correction_words from `leaves`; for the `wildcard`s, it
// additively shares them.
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
std::pair<
std::pair<leaf_type, beaver_type>,
std::pair<leaf_type, beaver_type> > return_tuple;
// N.B.: Despite the nesting, the loops below advance in lockstep, making
// only a single pass over each of the tuples being looped over
// loop over the original inputs (to interrogate their output_types)
std::apply([x, &sign, &return_tuple, &leaves](auto && ...y)
{
// loop over the elements of `leaves`, our "template" for a leaf tuple
std::apply([x, &sign, &return_tuple, &y...](auto && ...leaf)
{
// and also over the elements of `return_tuple.first.first`, the first leaf tuple
std::apply([x, &sign, &return_tuple, &y..., &leaf...](auto && ...leaf0)
{
// and also `return_tuple.second.first`, the secound leaf tuple
std::apply([x, &sign, &return_tuple, &y..., &leaf..., &leaf0...](auto && ...leaf1)
{
// plus `return_tuple.first.second`, the first beaver tuple
std::apply([x, &sign, &return_tuple, &y..., &leaf..., &leaf0..., &leaf1...](auto && ...beaver0)
{
// and `return_tuple.second.second`, the secound beaver tuple
std::apply([x, &sign, &y..., &leaf..., &leaf0..., &leaf1..., &beaver0...](auto && ...beaver1)
{
// lambda to decide whether to copy the leaf (for concrete output_types)
// or whether to secret share it (for wildcard output_types)
([](auto & x, auto & y, auto & leaf, auto & leaf0, auto & leaf1, auto & beaver0, auto & beaver1, bool sign)
{
using output_type = typename std::decay_t<decltype(y)>;
if constexpr(dpf::is_wildcard_v<output_type>)
{
using concrete_type = dpf::concrete_type_t<output_type>;
// secret share the value
dpf::uniform_fill(leaf0);
leaf1 = dpf::subtract_leaf<concrete_type>(leaf, leaf0);
// also initialize the beavers
if constexpr(!dpf::utils::has_characteristic_two_v<concrete_type>
|| dpf::outputs_per_leaf_v<concrete_type, node_type> > 1)
{
dpf::leaf_node_t<node_type, concrete_type> vector;
// XOR-group multiply is AND, whose unit is ~0, not ±1.
// Check the OUTPUT type: input may be modint while the
// leaf is xor_wrapper (wildcard XOR payload).
if constexpr(utils::is_xor_wrapper_v<std::decay_t<decltype(x)>> == true
|| utils::is_xor_wrapper_v<concrete_type> == true)
{
vector = make_naked_leaf<node_type>(x, concrete_type(~0));
}
else
{
vector = make_naked_leaf<node_type>(x, concrete_type(2*sign-1));
}
uniform_fill(beaver0.output_blind);
uniform_fill(beaver0.vector_blind);
uniform_fill(beaver1.output_blind);
uniform_fill(beaver1.vector_blind);
beaver0.blinded_vector = dpf::add_leaf<concrete_type>(vector, beaver1.vector_blind);
beaver1.blinded_vector = dpf::add_leaf<concrete_type>(vector, beaver0.vector_blind);
leaf0 = dpf::add_leaf<concrete_type>(leaf0,
dpf::multiply_leaf(beaver0.vector_blind, beaver1.output_blind));
leaf1 = dpf::add_leaf<concrete_type>(leaf1,
dpf::multiply_leaf(beaver1.vector_blind, beaver0.output_blind));
}
}
else
{
// copy concrete value; beaver is a trivial type
leaf0 = leaf;
leaf1 = leaf;
}
}(x, y, leaf, leaf0, leaf1, beaver0, beaver1, sign), ...);
}, return_tuple.second.second);
}, return_tuple.first.second);
}, return_tuple.second.first);
}, return_tuple.first.first);
}, leaves);
}, std::make_tuple(y, ys...));
HEDLEY_PRAGMA(GCC diagnostic pop)
return return_tuple;
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_LEAF_NODE_HPP__

View file

@ -0,0 +1,235 @@
/// @file dpf/leaf_wrapper.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_LEAF_WRAPPER_HPP__
#define LIBDPF_INCLUDE_DPF_LEAF_WRAPPER_HPP__
#include "hedley/hedley.h"
#include "dpf/secret_share.hpp"
namespace dpf
{
template <typename OutputT,
typename NodeT>
struct leaf_wrapper
{
public:
using leaf_type = dpf::leaf_node_t<NodeT, OutputT>;
using output_type = OutputT;
leaf_wrapper() = delete;
leaf_wrapper(leaf_type leaf, dpf::beaver<false, NodeT, OutputT> = dpf::beaver<false, NodeT, OutputT>{})
: leaf_{std::forward<leaf_type>(leaf)} { }
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr const leaf_type & get() const noexcept { return leaf_; }
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr const leaf_type & raw_leaf() const noexcept { return leaf_; }
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const dpf::beaver<false, NodeT, OutputT> & beaver() const noexcept
{
static const dpf::beaver<false, NodeT, OutputT> dummy{};
return dummy;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr bool is_ready() const noexcept { return true; }
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
static constexpr bool is_wildcard() noexcept { return false; }
private:
leaf_type leaf_;
};
// // unpacked wildcard reconstruction
// template <typename ConcreteOutputT,
// typename NodeT>
// struct leaf_wrapper<wildcard_value<ConcreteOutputT>, NodeT, false>
// {
// public:
// using leaf_type = dpf::leaf_node_t<NodeT, ConcreteOutputT>;
// using output_type = ConcreteOutputT;
// leaf_wrapper() = delete;
// leaf_wrapper(leaf_type leaf_share, dpf::beaver<NodeT, output_type> = dpf::beaver<NodeT, output_type>{})
// : leaf_{leaf_share},
// leaf_state_(std::make_unique<std::atomic<leaf_status>>(leaf_status::notset)),
// ready_{false} { }
// HEDLEY_ALWAYS_INLINE
// const leaf_type & get() const
// {
// if (HEDLEY_UNLIKELY(!ready_))
// {
// throw std::runtime_error("offset not set");
// }
// return leaf_;
// }
// const leaf_type compute_and_get_leaf_share(output_type output_share)
// {
// leaf_status notset = leaf_status::notset;
// if (HEDLEY_UNLIKELY(!leaf_state_->compare_exchange_strong(notset,
// leaf_status::computing,
// std::memory_order_seq_cst, std::memory_order_relaxed)))
// {
// throw std::runtime_error("invalid state transition");
// }
// leaf_type tmp;
// std::memcpy(&tmp, &output_share, sizeof(output_type));
// leaf_ = add_leaf<output_type>(leaf_, tmp);
// leaf_state_->store(leaf_status::waiting, std::memory_order_release);
// return leaf_;
// }
// const leaf_type reconstruct_correction_word(leaf_type other_share)
// {
// leaf_status waiting = leaf_status::waiting;
// if (HEDLEY_UNLIKELY(!leaf_state_->compare_exchange_strong(waiting,
// leaf_status::computing,
// std::memory_order_acquire, std::memory_order_relaxed)))
// {
// throw std::runtime_error("invalid state transition");
// }
// leaf_ = add_leaf<output_type>(leaf_, other_share);
// ready_ = true;
// leaf_state_->store(leaf_status::ready, std::memory_order_relaxed);
// return leaf_;
// }
// HEDLEY_ALWAYS_INLINE
// HEDLEY_NO_THROW
// bool is_ready() const noexcept { return ready_; }
// HEDLEY_ALWAYS_INLINE
// HEDLEY_PURE
// HEDLEY_NO_THROW
// static constexpr bool is_wildcard() noexcept { return true; }
// // private:
// enum class leaf_status : psnip_uint8_t { ready = 0, waiting = 1, computing = 2, notset = 3 };
// leaf_type leaf_;
// std::unique_ptr<std::atomic<leaf_status>> leaf_state_;
// bool ready_;
// };
template <typename ConcreteOutputT,
typename NodeT>
struct leaf_wrapper<wildcard_value<ConcreteOutputT>, NodeT>
{
public:
using node_type = NodeT;
using output_type = ConcreteOutputT;
using leaf_type = dpf::leaf_node_t<node_type, output_type>;
using beaver_type = dpf::beaver<true, node_type, output_type>;
leaf_wrapper() = delete;
leaf_wrapper(leaf_type leaf_share, beaver_type beaver)
: leaf_{std::forward<leaf_type>(leaf_share)},
beaver_{beaver},
output_share_{},
leaf_state_{leaf_status::notset}
{ }
HEDLEY_ALWAYS_INLINE
const leaf_type & get() const
{
if (HEDLEY_UNLIKELY(leaf_state_ != leaf_status::ready))
{
throw std::runtime_error("offset not set");
}
return leaf_;
}
const output_type compute_and_get_blinded_output_share(output_type output_share)
{
begin_transition(leaf_status::notset);
output_share_ = output_share;
auto blinded_output_share = output_share_ + beaver_.output_blind;
leaf_state_ = leaf_status::blinded;
return blinded_output_share;
}
/// Accept a party-tagged share; convert to additive before Beaver math.
template <std::size_t Party, sharing Scheme>
const output_type compute_and_get_blinded_output_share(
const secret_share<output_type, Party, Scheme> & output_share)
{
return compute_and_get_blinded_output_share(
output_share.as_additive().raw());
}
const leaf_type compute_and_get_leaf_share(output_type other_output_share)
{
begin_transition(leaf_status::blinded);
leaf_ = add_leaf<output_type>(leaf_, subtract_leaf<output_type>(
multiply_leaf(beaver_.blinded_vector, output_share_),
multiply_leaf(beaver_.vector_blind, other_output_share)));
leaf_state_ = leaf_status::waiting;
return leaf_;
}
const leaf_type reconstruct_correction_word(leaf_type other_share)
{
begin_transition(leaf_status::waiting);
leaf_ = add_leaf<output_type>(leaf_, other_share);
leaf_state_ = leaf_status::ready;
return leaf_;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
bool is_ready() const noexcept { return leaf_state_ == leaf_status::ready; }
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
static constexpr bool is_wildcard() noexcept { return true; }
// Unassigned leaf / Beaver (before online payload). get() throws until ready.
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const leaf_type & raw_leaf() const noexcept { return leaf_; }
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
const beaver_type & beaver() const noexcept { return beaver_; }
private:
enum class leaf_status : psnip_uint8_t { ready = 0, waiting = 1, computing = 2, blinded = 3, notset = 4 };
void begin_transition(leaf_status expected)
{
if (HEDLEY_UNLIKELY(leaf_state_ != expected))
{
throw std::runtime_error("invalid state transition");
}
leaf_state_ = leaf_status::computing;
}
leaf_type leaf_;
beaver_type beaver_;
output_type output_share_;
leaf_status leaf_state_;
};
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_LEAF_WRAPPER_HPP__

23
include/dpf/literals.hpp Normal file
View file

@ -0,0 +1,23 @@
#ifndef LIBDPF_INCLUDE_DPF_LITERALS_HPP__
#define LIBDPF_INCLUDE_DPF_LITERALS_HPP__
#include "dpf/twobit.hpp"
#include "dpf/nyble.hpp"
namespace dpf
{
namespace literals
{
namespace modints{} using namespace dpf::literals::modints;
namespace xints{} using namespace dpf::literals::xints;
namespace bitstrings{} using namespace dpf::literals::bitstrings;
namespace twobit{} using namespace dpf::literals::twobit;
namespace nyble{} using namespace dpf::literals::nyble;
} // namespace literals
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_LITERALS_HPP__

1397
include/dpf/modint.hpp Normal file

File diff suppressed because it is too large Load diff

209
include/dpf/nyble.hpp Normal file
View file

@ -0,0 +1,209 @@
/// @file dpf/nyble.hpp
/// @brief `dpf::nyble`, a 4-bit output lane in the ring Z/16Z.
/// @details Values are `0..15`. Scalar `+` and `-` wrap mod 16. A leaf node
/// packs one lane every four bits, low nibble first. Leaf addition
/// is not XOR and is not `add_epi8`: a carry must not cross into
/// the neighbouring nibble. See `packed_lane_arithmetic.hpp`.
#ifndef LIBDPF_INCLUDE_DPF_NYBLE_HPP__
#define LIBDPF_INCLUDE_DPF_NYBLE_HPP__
#include <cstddef>
#include <cstdint>
#include <istream>
#include <limits>
#include <ostream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include "hedley/hedley.h"
#include "dpf/utils.hpp"
namespace dpf
{
/// @brief 4-bit unsigned ring element, packed one nibble per lane
enum class nyble : std::uint8_t
{
zero = 0
};
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
static constexpr dpf::nyble to_nyble(unsigned value) noexcept
{
return static_cast<dpf::nyble>(value & 0x0fu);
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
static constexpr dpf::nyble to_nyble(unsigned long long value) noexcept
{
return static_cast<dpf::nyble>(value & 0x0full);
}
/// @brief parse one hex digit as a nibble
/// @throws std::domain_error if `value` is not `0-9`, `a-f`, or `A-F`
template <typename CharT>
static constexpr dpf::nyble to_nyble(CharT value)
{
auto u = static_cast<unsigned>(value);
if (u >= static_cast<unsigned>(CharT('0')) && u <= static_cast<unsigned>(CharT('9')))
{
return static_cast<dpf::nyble>(u - static_cast<unsigned>(CharT('0')));
}
if (u >= static_cast<unsigned>(CharT('a')) && u <= static_cast<unsigned>(CharT('f')))
{
return static_cast<dpf::nyble>(10u + u - static_cast<unsigned>(CharT('a')));
}
if (u >= static_cast<unsigned>(CharT('A')) && u <= static_cast<unsigned>(CharT('F')))
{
return static_cast<dpf::nyble>(10u + u - static_cast<unsigned>(CharT('A')));
}
throw std::domain_error("Unrecognized nyble character");
}
inline std::string to_string(dpf::nyble value)
{
constexpr char digits[] = "0123456789abcdef";
return std::string(1, digits[static_cast<unsigned>(value) & 0x0fu]);
}
template <typename CharT, typename Traits>
std::basic_ostream<CharT, Traits> &
operator<<(std::basic_ostream<CharT, Traits> & os, dpf::nyble value)
{
constexpr char digits[] = "0123456789abcdef";
return os << digits[static_cast<unsigned>(value) & 0x0fu];
}
template <typename CharT, typename Traits>
std::basic_istream<CharT, Traits> &
operator>>(std::basic_istream<CharT, Traits> & is, dpf::nyble & value)
{
try
{
value = to_nyble<CharT>(is.get());
}
catch (const std::exception &)
{
is.setstate(std::ios::failbit);
}
return is;
}
/// @brief addition in Z/16Z
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr dpf::nyble operator+(dpf::nyble lhs, dpf::nyble rhs) noexcept
{
return static_cast<dpf::nyble>(
(static_cast<unsigned>(lhs) + static_cast<unsigned>(rhs)) & 0x0fu);
}
/// @brief subtraction in Z/16Z
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr dpf::nyble operator-(dpf::nyble lhs, dpf::nyble rhs) noexcept
{
return static_cast<dpf::nyble>(
(static_cast<unsigned>(lhs) - static_cast<unsigned>(rhs)) & 0x0fu);
}
/// @brief additive inverse in Z/16Z
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr dpf::nyble operator-(dpf::nyble value) noexcept
{
return static_cast<dpf::nyble>((0u - static_cast<unsigned>(value)) & 0x0fu);
}
/// @brief multiplication in Z/16Z
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr dpf::nyble operator*(dpf::nyble lhs, dpf::nyble rhs) noexcept
{
return static_cast<dpf::nyble>(
(static_cast<unsigned>(lhs) * static_cast<unsigned>(rhs)) & 0x0fu);
}
namespace utils
{
template <>
struct bitlength_of<dpf::nyble>
: public std::integral_constant<std::size_t, 4> {};
template <typename NodeT>
struct bitlength_of_output<dpf::nyble, NodeT>
: public std::integral_constant<std::size_t, 4> {};
template <>
struct is_packed_subbyte<dpf::nyble> : std::true_type {};
template <>
struct packed_lane_bits<dpf::nyble>
: public std::integral_constant<std::size_t, 4> {};
template <>
struct make_from_integral_value<dpf::nyble>
{
using integral_type = std::uint8_t;
constexpr dpf::nyble operator()(integral_type val) const noexcept
{
return dpf::to_nyble(val);
}
};
} // namespace utils
namespace literals
{
namespace nyble
{
constexpr dpf::nyble operator""_nyble(unsigned long long x)
{
return dpf::to_nyble(x);
}
} // namespace nyble
} // namespace literals
} // namespace dpf
namespace std
{
template <>
class numeric_limits<dpf::nyble> : public numeric_limits<std::uint8_t>
{
public:
static constexpr int digits = 4;
static constexpr int digits10 = 1;
static constexpr dpf::nyble min() noexcept { return dpf::nyble::zero; }
static constexpr dpf::nyble max() noexcept { return dpf::nyble{15}; }
static constexpr dpf::nyble lowest() noexcept { return min(); }
};
template <>
class numeric_limits<dpf::nyble const> : public numeric_limits<dpf::nyble> {};
template <>
class numeric_limits<dpf::nyble volatile> : public numeric_limits<dpf::nyble> {};
template <>
class numeric_limits<dpf::nyble const volatile>
: public numeric_limits<dpf::nyble> {};
} // namespace std
#endif // LIBDPF_INCLUDE_DPF_NYBLE_HPP__

View file

@ -0,0 +1,129 @@
/// @file dpf/offset_wrapper.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_OFFSET_WRAPPER_HPP__
#define LIBDPF_INCLUDE_DPF_OFFSET_WRAPPER_HPP__
#include "hedley/hedley.h"
namespace dpf
{
template <typename InputT>
struct offset_wrapper final
{
public:
using input_type = dpf::concrete_type_t<InputT>;
offset_wrapper(input_type = input_type{})
: offset_{} { }
template <typename InputType>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr auto operator()(InputType && x) const noexcept
{
static_assert(std::is_convertible_v<InputType, input_type>);
return input_type(std::forward<InputType>(x));
}
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr bool is_ready() const noexcept { return true; }
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
static constexpr bool is_wildcard() noexcept { return false; }
private:
input_type offset_; // waste an `input_type` to make `sizeof` match up
};
template <typename ConcreteInputT>
struct offset_wrapper<dpf::wildcard_value<ConcreteInputT>>
{
public:
using input_type = ConcreteInputT;
offset_wrapper(input_type x)
: offset_{x},
offset_state_{offset_status::notset}
{ }
template <typename InputType>
HEDLEY_INLINE
input_type operator()(InputType && x) const
{
static_assert(std::is_convertible_v<InputType, input_type>);
if (HEDLEY_UNLIKELY(offset_state_ != offset_status::ready))
{
throw std::runtime_error("offset not set");
}
return input_type(x) + offset_;
}
template <typename InputType>
const input_type & compute_and_get_share(InputType && input_share)
{
static_assert(std::is_convertible_v<InputType, input_type>);
begin_transition(offset_status::notset);
offset_ -= input_share;
offset_state_ = offset_status::waiting;
return offset_;
}
template <typename InputType>
const input_type & reconstruct(InputType && other_share)
{
static_assert(std::is_convertible_v<InputType, input_type>);
begin_transition(offset_status::waiting);
offset_ += other_share;
offset_state_ = offset_status::ready;
return offset_;
}
template <typename InputType>
const input_type & set(InputType && offset)
{
static_assert(std::is_convertible_v<InputType, input_type>);
begin_transition(offset_status::notset);
offset_ += offset;
offset_state_ = offset_status::ready;
return offset_;
}
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
bool is_ready() const noexcept { return offset_state_ == offset_status::ready; }
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
static constexpr bool is_wildcard() noexcept { return true; }
private:
enum class offset_status : psnip_uint8_t { ready = 0, waiting = 1, computing = 2, notset = 3 };
void begin_transition(offset_status expected)
{
if (HEDLEY_UNLIKELY(offset_state_ != expected))
{
throw std::runtime_error("invalid state transition");
}
offset_state_ = offset_status::computing;
}
input_type offset_;
offset_status offset_state_;
};
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_OFFSET_WRAPPER_HPP__

View file

@ -0,0 +1,465 @@
/// @file dpf/output_buffer.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_OUTPUT_BUFFER_HPP__
#define LIBDPF_INCLUDE_DPF_OUTPUT_BUFFER_HPP__
#include <cstddef>
#include <algorithm>
#include <tuple>
#include <limits>
#include <iterator>
#include <new>
#include <type_traits>
#include <vector>
#include "dpf/aligned_allocator.hpp"
#include "dpf/leaf_node.hpp"
#include "dpf/utils.hpp"
#include "dpf/bit.hpp"
#include "dpf/bit_array.hpp"
#include "dpf/packed_array.hpp"
#include "dpf/secret_share.hpp"
#include "dpf/sequence_recipe.hpp"
#include "dpf/sequence_utils.hpp"
namespace dpf
{
/// Buffer element type for leaf eval of `KeyT`: party-tagged subtractive
/// share when `KeyT` is a `party_key`, otherwise the concrete output.
template <typename KeyT, typename OutputT, bool = is_party_key_v<KeyT>>
struct leaf_buffer_elem
{
using type = OutputT;
};
template <typename KeyT, typename OutputT>
struct leaf_buffer_elem<KeyT, OutputT, true>
{
using type = subtractive_share<OutputT, party_of_v<KeyT>>;
};
template <typename KeyT, typename OutputT>
using leaf_buffer_elem_t = typename leaf_buffer_elem<KeyT, OutputT>::type;
/// Buffer element type for comparison eval of `KeyT`.
template <typename KeyT, typename Beta, bool = is_party_key_v<KeyT>>
struct cmp_buffer_elem
{
using type = Beta;
};
template <typename KeyT, typename Beta>
struct cmp_buffer_elem<KeyT, Beta, true>
{
using type = additive_share<Beta, party_of_v<KeyT>>;
};
template <typename KeyT, typename Beta>
using cmp_buffer_elem_t = typename cmp_buffer_elem<KeyT, Beta>::type;
/// `std::vector(n)` value-initializes every slot. Interval / full eval
/// overwrites the whole buffer, so skip default-construction for trivial
/// `T`. Non-trivial outputs still run their default constructor.
template <typename T,
std::size_t Alignment>
class output_buffer_allocator : public aligned_allocator<T, Alignment>
{
public:
using is_always_equal = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
template <typename U>
struct rebind
{
using other = output_buffer_allocator<U, Alignment>;
};
output_buffer_allocator() noexcept = default;
output_buffer_allocator(const output_buffer_allocator &) noexcept = default;
template <typename U>
output_buffer_allocator(const output_buffer_allocator<U, Alignment> &) noexcept {}
template <typename U>
void construct(U * p)
noexcept(std::is_nothrow_default_constructible_v<U>)
{
if constexpr (!std::is_trivially_default_constructible_v<U>)
{
::new (static_cast<void *>(p)) U();
}
}
template <typename U, typename A0, typename ...Args>
void construct(U * p, A0 && a0, Args && ...args)
{
::new (static_cast<void *>(p)) U(std::forward<A0>(a0),
std::forward<Args>(args)...);
}
template <typename U>
void destroy(U * p) noexcept
{
if constexpr (!std::is_trivially_destructible_v<U>)
{
p->~U();
}
}
};
template <typename T, std::size_t A, typename U, std::size_t B>
constexpr bool operator==(const output_buffer_allocator<T, A> &,
const output_buffer_allocator<U, B> &) noexcept
{
return A == B;
}
template <typename T, std::size_t A, typename U, std::size_t B>
constexpr bool operator!=(const output_buffer_allocator<T, A> & lhs,
const output_buffer_allocator<U, B> & rhs) noexcept
{
return !(lhs == rhs);
}
template <typename T,
std::size_t Alignment = utils::max_align_v>
class output_buffer final
: private std::vector<T, dpf::output_buffer_allocator<T, Alignment>>
{
private:
using vector = std::vector<T, dpf::output_buffer_allocator<T, Alignment>>;
public:
using value_type = typename vector::value_type;
using iterator = typename vector::iterator;
using const_iterator = typename vector::const_iterator;
using size_type = typename vector::size_type;
output_buffer() noexcept = default;
explicit output_buffer(size_type size) : vector(size) { }
output_buffer(output_buffer &&) noexcept = default;
output_buffer(const output_buffer &) = delete;
output_buffer & operator=(output_buffer &&) noexcept = default;
output_buffer & operator=(const output_buffer &) = delete;
~output_buffer() = default;
// "selectively public" inheritance
using vector::at;
using vector::operator[];
using vector::data;
using vector::begin;
using vector::cbegin;
using vector::end;
using vector::cend;
using vector::size;
};
template <>
class output_buffer<dpf::bit> : public dpf::dynamic_bit_array<>
{
private:
using size_type = typename dpf::dynamic_bit_array<>::size_type;
public:
explicit output_buffer(size_type size) : dynamic_bit_array(size) { }
output_buffer(output_buffer &&) noexcept = default;
output_buffer(const output_buffer &) = delete;
output_buffer & operator=(output_buffer &&) noexcept = default;
output_buffer & operator=(const output_buffer &) = delete;
~output_buffer() = default;
};
template <>
class output_buffer<dpf::twobit> : public dpf::dynamic_packed_array<dpf::twobit>
{
using base = dpf::dynamic_packed_array<dpf::twobit>;
public:
using size_type = typename base::size_type;
explicit output_buffer(size_type size) : base(size) { }
output_buffer(output_buffer &&) noexcept = default;
output_buffer(const output_buffer &) = delete;
output_buffer & operator=(output_buffer &&) noexcept = default;
output_buffer & operator=(const output_buffer &) = delete;
~output_buffer() = default;
};
template <>
class output_buffer<dpf::nyble> : public dpf::dynamic_packed_array<dpf::nyble>
{
using base = dpf::dynamic_packed_array<dpf::nyble>;
public:
using size_type = typename base::size_type;
explicit output_buffer(size_type size) : base(size) { }
output_buffer(output_buffer &&) noexcept = default;
output_buffer(const output_buffer &) = delete;
output_buffer & operator=(output_buffer &&) noexcept = default;
output_buffer & operator=(const output_buffer &) = delete;
~output_buffer() = default;
};
#define LIBDPF_PACKED_SHARE_BUFFER(LANE, PARTY) \
template <> \
class output_buffer<subtractive_share<LANE, PARTY>> \
: public packed_share_output<LANE, PARTY> \
{ \
using base = packed_share_output<LANE, PARTY>; \
public: \
using size_type = typename base::size_type; \
explicit output_buffer(size_type size) : base(size) {} \
output_buffer(output_buffer &&) noexcept = default; \
output_buffer(const output_buffer &) = delete; \
output_buffer & operator=(output_buffer &&) noexcept = default; \
output_buffer & operator=(const output_buffer &) = delete; \
~output_buffer() = default; \
};
LIBDPF_PACKED_SHARE_BUFFER(dpf::twobit, 0);
LIBDPF_PACKED_SHARE_BUFFER(dpf::twobit, 1);
LIBDPF_PACKED_SHARE_BUFFER(dpf::nyble, 0);
LIBDPF_PACKED_SHARE_BUFFER(dpf::nyble, 1);
#undef LIBDPF_PACKED_SHARE_BUFFER
/// Packed bit share buffers reuse the bit-array image; iterators yield shares.
#define LIBDPF_BIT_SHARE_BUFFER(PARTY) \
template <> \
class output_buffer<subtractive_share<dpf::bit, PARTY>> \
: public dpf::dynamic_bit_array<> \
{ \
private: \
using size_type = typename dpf::dynamic_bit_array<>::size_type; \
public: \
explicit output_buffer(size_type size) : dynamic_bit_array(size) {} \
output_buffer(output_buffer &&) noexcept = default; \
output_buffer(const output_buffer &) = delete; \
output_buffer & operator=(output_buffer &&) noexcept = default; \
output_buffer & operator=(const output_buffer &) = delete; \
~output_buffer() = default; \
};
LIBDPF_BIT_SHARE_BUFFER(0);
LIBDPF_BIT_SHARE_BUFFER(1);
#undef LIBDPF_BIT_SHARE_BUFFER
template <typename DpfKey,
std::size_t I = 0,
typename InputT>
auto make_output_buffer_for_interval(InputT from, InputT to)
{
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
using buffer_elem = leaf_buffer_elem_t<DpfKey, output_type>;
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
std::size_t nodes_in_interval = utils::get_leafnodes_in_output_interval<dpf_type>(from, to);
return dpf::output_buffer<buffer_elem>(nodes_in_interval*dpf_type::outputs_per_leaf);
}
template <typename DpfKey,
std::size_t I0,
std::size_t I1,
std::size_t ...Is,
typename InputT>
auto make_output_buffer_for_interval(InputT from, InputT to)
{
return std::make_tuple(
make_output_buffer_for_interval<DpfKey, I0>(from, to),
make_output_buffer_for_interval<DpfKey, I1>(from, to),
make_output_buffer_for_interval<DpfKey, Is>(from, to)...);
}
template <std::size_t I = 0,
typename DpfKey,
typename InputT>
inline auto make_output_buffer_for_interval(const DpfKey &, InputT from, InputT to)
{
return make_output_buffer_for_interval<DpfKey, I>(from, to);
}
template <std::size_t I0,
std::size_t I1,
std::size_t ...Is,
typename DpfKey,
typename InputT>
inline auto make_output_buffer_for_interval(const DpfKey &, InputT from, InputT to)
{
return make_output_buffer_for_interval<DpfKey, I0, I1, Is...>(from, to);
}
template <typename DpfKey,
std::size_t I = 0>
auto make_output_buffer_for_full()
{
using dpf_type = DpfKey;
using input_type = typename dpf_type::input_type;
return make_output_buffer_for_interval<dpf_type, I>(
std::numeric_limits<input_type>::min(),
std::numeric_limits<input_type>::max());
}
template <typename DpfKey,
std::size_t I0,
std::size_t I1,
std::size_t ...Is>
auto make_output_buffer_for_full()
{
return std::make_tuple(
make_output_buffer_for_full<DpfKey, I0>(),
make_output_buffer_for_full<DpfKey, I1>(),
make_output_buffer_for_full<DpfKey, Is>()...);
}
template <std::size_t I = 0,
typename DpfKey>
inline auto make_output_buffer_for_full(const DpfKey &)
{
return make_output_buffer_for_full<DpfKey, I>();
}
template <std::size_t I0,
std::size_t I1,
std::size_t ...Is,
typename DpfKey>
inline auto make_output_buffer_for_full(const DpfKey &)
{
return make_output_buffer_for_full<DpfKey, I0, I1, Is...>();
}
template <typename DpfKey,
std::size_t I = 0,
typename ForwardIterator,
typename ReturnType = return_entire_node_tag_>
auto make_output_buffer_for_subsequence(ForwardIterator begin, ForwardIterator end, ReturnType return_type = ReturnType{})
{
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
using buffer_elem = leaf_buffer_elem_t<DpfKey, output_type>;
std::size_t points_in_sequence = std::distance(begin, end);
static_assert(std::is_same_v<ReturnType, return_entire_node_tag_> ||
std::is_same_v<ReturnType, return_output_only_tag_>);
if constexpr(std::is_same_v<ReturnType, return_entire_node_tag_>)
{
return dpf::output_buffer<buffer_elem>(points_in_sequence*dpf_type::outputs_per_leaf);
}
else
{
if constexpr(std::is_same_v<typename DpfKey::concrete_output_type<0>, dpf::bit>)
{
auto tmp = dpf::output_buffer<buffer_elem>(points_in_sequence);
tmp.unset();
return std::move(tmp);
}
else
{
return dpf::output_buffer<buffer_elem>(points_in_sequence);
}
}
}
template <typename DpfKey,
std::size_t I0,
std::size_t I1,
std::size_t ...Is,
typename ForwardIterator,
typename ReturnType = return_entire_node_tag_>
auto make_output_buffer_for_subsequence(ForwardIterator begin, ForwardIterator end, ReturnType return_type = ReturnType{})
{
return std::make_tuple(
make_output_buffer_for_subsequence<DpfKey, I0>(begin, end, return_type),
make_output_buffer_for_subsequence<DpfKey, I1>(begin, end, return_type),
make_output_buffer_for_subsequence<DpfKey, Is>(begin, end, return_type)...);
}
template <std::size_t I = 0,
typename DpfKey,
typename ForwardIterator,
typename ReturnType = return_entire_node_tag_>
inline auto make_output_buffer_for_subsequence(const DpfKey &, ForwardIterator begin, ForwardIterator end, ReturnType return_type = ReturnType{})
{
return make_output_buffer_for_subsequence<DpfKey, I>(begin, end, return_type);
}
template <std::size_t I0,
std::size_t I1,
std::size_t ...Is,
typename DpfKey,
typename ForwardIterator,
typename ReturnType = return_entire_node_tag_>
inline auto make_output_buffer_for_subsequence(const DpfKey &, ForwardIterator begin, ForwardIterator end, ReturnType return_type = ReturnType{})
{
return make_output_buffer_for_subsequence<DpfKey, I0, I1, Is...>(begin, end, return_type);
}
template <typename DpfKey,
std::size_t I = 0,
typename ReturnType = return_entire_node_tag_>
auto make_output_buffer_for_recipe_subsequence(const sequence_recipe & recipe, ReturnType return_type = ReturnType{})
{
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
using buffer_elem = leaf_buffer_elem_t<DpfKey, output_type>;
static_assert(std::is_same_v<ReturnType, return_entire_node_tag_> ||
std::is_same_v<ReturnType, return_output_only_tag_>);
if constexpr(std::is_same_v<ReturnType, return_entire_node_tag_>)
{
return dpf::output_buffer<buffer_elem>(recipe.num_leaf_nodes()*dpf_type::outputs_per_leaf);
}
else
{
if constexpr(std::is_same_v<typename DpfKey::concrete_output_type<0>, dpf::bit>)
{
auto tmp = dpf::output_buffer<buffer_elem>(recipe.output_indices().size());
tmp.unset();
return std::move(tmp);
}
else
{
return dpf::output_buffer<buffer_elem>(recipe.output_indices().size());
}
}
}
template <typename DpfKey,
std::size_t I0,
std::size_t I1,
std::size_t ...Is,
typename ReturnType = return_entire_node_tag_>
inline auto make_output_buffer_for_recipe_subsequence(const sequence_recipe & recipe, ReturnType return_type = ReturnType{})
{
return std::make_tuple(
make_output_buffer_for_recipe_subsequence<DpfKey, I0>(recipe, return_type),
make_output_buffer_for_recipe_subsequence<DpfKey, I1>(recipe, return_type),
make_output_buffer_for_recipe_subsequence<DpfKey, Is>(recipe, return_type)...);
}
template <std::size_t I = 0,
typename DpfKey,
typename ReturnType = return_entire_node_tag_>
inline auto make_output_buffer_for_recipe_subsequence(const DpfKey &, const sequence_recipe & recipe, ReturnType return_type = ReturnType{})
{
return make_output_buffer_for_recipe_subsequence<DpfKey, I>(recipe, return_type);
}
template <std::size_t I0,
std::size_t I1,
std::size_t ...Is,
typename DpfKey,
typename ReturnType = return_entire_node_tag_>
inline auto make_output_buffer_for_recipe_subsequence(const DpfKey &, const sequence_recipe & recipe, ReturnType return_type = ReturnType{})
{
return make_output_buffer_for_recipe_subsequence<DpfKey, I0, I1, Is...>(recipe, return_type);
}
namespace utils
{
template <>
struct is_bit_array<output_buffer<bit>> : std::true_type {};
} // namespace utils
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_OUTPUT_BUFFER_HPP__

View file

@ -0,0 +1,411 @@
/// @file dpf/packed_array.hpp
/// @brief Dynamic array of `dpf::twobit` or `dpf::nyble` lanes.
/// @details Storage is the same bit layout as a packed leaf, so
/// `store_leaf_bytes` can memcpy a node at lane `index`.
/// Iterators hold a pointer to that storage and stay valid when
/// the owning buffer is moved.
#ifndef LIBDPF_INCLUDE_DPF_PACKED_ARRAY_HPP__
#define LIBDPF_INCLUDE_DPF_PACKED_ARRAY_HPP__
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <memory>
#include <new>
#include <utility>
#include "hedley/hedley.h"
#include "dpf/aligned_allocator.hpp"
#include "dpf/nyble.hpp"
#include "dpf/secret_share.hpp"
#include "dpf/twobit.hpp"
#include "dpf/utils.hpp"
namespace dpf
{
template <typename LaneT>
class dynamic_packed_array
{
static constexpr std::size_t lane_bits = utils::packed_lane_bits_v<LaneT>;
static_assert(lane_bits == 2 || lane_bits == 4,
"dynamic_packed_array lanes are 2 or 4 bits");
static constexpr std::size_t lanes_per_word = 64u / lane_bits;
static constexpr unsigned lane_mask = (1u << lane_bits) - 1u;
public:
using value_type = LaneT;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using word_type = std::uint64_t;
private:
using allocator = aligned_allocator<word_type, utils::max_align_v>;
using unique_ptr = typename allocator::unique_ptr;
class lane_ref
{
public:
lane_ref(word_type * word, unsigned shift) noexcept
: word_{word}, shift_{shift} {}
HEDLEY_ALWAYS_INLINE
operator LaneT() const noexcept
{
return static_cast<LaneT>((*word_ >> shift_) & lane_mask);
}
HEDLEY_ALWAYS_INLINE
lane_ref & operator=(LaneT value) noexcept
{
const auto val = static_cast<word_type>(
static_cast<unsigned>(value) & lane_mask);
*word_ = (*word_ & ~(static_cast<word_type>(lane_mask) << shift_))
| (val << shift_);
return *this;
}
lane_ref & operator=(const lane_ref & other) noexcept
{
return (*this = static_cast<LaneT>(other));
}
friend bool operator==(lane_ref lhs, LaneT rhs) noexcept
{
return static_cast<LaneT>(lhs) == rhs;
}
friend bool operator==(LaneT lhs, lane_ref rhs) noexcept
{
return rhs == lhs;
}
friend bool operator!=(lane_ref lhs, LaneT rhs) noexcept
{
return !(lhs == rhs);
}
friend bool operator!=(LaneT lhs, lane_ref rhs) noexcept
{
return !(rhs == lhs);
}
private:
word_type * word_;
unsigned shift_;
};
public:
using reference = lane_ref;
class iterator
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = LaneT;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = lane_ref;
iterator() noexcept = default;
iterator(word_type * data, size_type index) noexcept
: data_{data}, index_{index} {}
lane_ref operator*() const noexcept { return ref_at(index_); }
lane_ref operator[](difference_type n) const noexcept
{
return ref_at(static_cast<size_type>(
static_cast<difference_type>(index_) + n));
}
iterator & operator++() noexcept { ++index_; return *this; }
iterator operator++(int) noexcept
{
iterator prev = *this;
++*this;
return prev;
}
iterator & operator--() noexcept { --index_; return *this; }
iterator operator--(int) noexcept
{
iterator prev = *this;
--*this;
return prev;
}
iterator & operator+=(difference_type n) noexcept
{
index_ = static_cast<size_type>(
static_cast<difference_type>(index_) + n);
return *this;
}
iterator & operator-=(difference_type n) noexcept
{
return *this += -n;
}
friend iterator operator+(iterator it, difference_type n) noexcept
{
it += n;
return it;
}
friend iterator operator+(difference_type n, iterator it) noexcept
{
return it + n;
}
friend iterator operator-(iterator it, difference_type n) noexcept
{
it -= n;
return it;
}
friend difference_type operator-(iterator a, iterator b) noexcept
{
return static_cast<difference_type>(a.index_)
- static_cast<difference_type>(b.index_);
}
friend bool operator==(iterator a, iterator b) noexcept
{
return a.index_ == b.index_;
}
friend bool operator!=(iterator a, iterator b) noexcept
{
return !(a == b);
}
friend bool operator<(iterator a, iterator b) noexcept
{
return a.index_ < b.index_;
}
friend bool operator>(iterator a, iterator b) noexcept { return b < a; }
friend bool operator<=(iterator a, iterator b) noexcept { return !(b < a); }
friend bool operator>=(iterator a, iterator b) noexcept { return !(a < b); }
private:
lane_ref ref_at(size_type index) const noexcept
{
const size_type bit = index * lane_bits;
return lane_ref(data_ + (bit / 64u),
static_cast<unsigned>(bit % 64u));
}
word_type * data_ = nullptr;
size_type index_ = 0;
};
using const_iterator = iterator;
explicit dynamic_packed_array(size_type nlanes)
: nlanes_{nlanes},
nwords_{utils::quotient_ceiling(nlanes, lanes_per_word)}
{
if (nwords_ == 0)
{
return;
}
data_ = allocator{}.allocate_unique_ptr(nwords_);
if (HEDLEY_UNLIKELY(data_ == nullptr))
{
throw std::bad_alloc{};
}
std::fill_n(data_.get(), nwords_, word_type{0});
}
dynamic_packed_array(const dynamic_packed_array &) = delete;
dynamic_packed_array & operator=(const dynamic_packed_array &) = delete;
dynamic_packed_array(dynamic_packed_array && other) noexcept
: nlanes_{std::exchange(other.nlanes_, 0)},
nwords_{std::exchange(other.nwords_, 0)},
data_{std::move(other.data_)}
{}
dynamic_packed_array & operator=(dynamic_packed_array && other) noexcept
{
if (this != &other)
{
nlanes_ = std::exchange(other.nlanes_, 0);
nwords_ = std::exchange(other.nwords_, 0);
data_ = std::move(other.data_);
}
return *this;
}
~dynamic_packed_array() = default;
size_type size() const noexcept { return nlanes_; }
bool empty() const noexcept { return nlanes_ == 0; }
size_type data_length() const noexcept { return nwords_; }
word_type * data() noexcept { return data_.get(); }
const word_type * data() const noexcept { return data_.get(); }
LaneT operator[](size_type i) const noexcept
{
assert(i < nlanes_);
const size_type bit = i * lane_bits;
const unsigned shift = static_cast<unsigned>(bit % 64u);
return static_cast<LaneT>((data_[bit / 64u] >> shift) & lane_mask);
}
lane_ref operator[](size_type i) noexcept
{
assert(i < nlanes_);
const size_type bit = i * lane_bits;
return lane_ref(data_.get() + (bit / 64u),
static_cast<unsigned>(bit % 64u));
}
iterator begin() noexcept { return iterator{data(), 0}; }
iterator end() noexcept { return iterator{data(), nlanes_}; }
iterator begin() const noexcept { return iterator{data_.get(), 0}; }
iterator end() const noexcept { return iterator{data_.get(), nlanes_}; }
iterator cbegin() const noexcept { return begin(); }
iterator cend() const noexcept { return end(); }
private:
size_type nlanes_ = 0;
size_type nwords_ = 0;
unique_ptr data_{};
};
/// Packed lane storage whose iterators yield `subtractive_share<LaneT, Party>`.
/// The bytes are the leaf image (`store_leaf_bytes`); each lane is one share.
template <typename LaneT, std::size_t Party>
class packed_share_output : public dynamic_packed_array<LaneT>
{
using lanes = dynamic_packed_array<LaneT>;
using share_type = subtractive_share<LaneT, Party>;
public:
using value_type = share_type;
using size_type = typename lanes::size_type;
using difference_type = typename lanes::difference_type;
class reference
{
public:
explicit reference(typename lanes::reference lane) noexcept : lane_{lane} {}
operator share_type() const noexcept
{
return share_type::from_raw(static_cast<LaneT>(lane_));
}
reference & operator=(const share_type & share) noexcept
{
lane_ = share.raw();
return *this;
}
reference & operator=(LaneT value) noexcept
{
lane_ = value;
return *this;
}
reference & operator=(const reference & other) noexcept
{
return (*this = static_cast<share_type>(other));
}
private:
typename lanes::reference lane_;
};
class iterator
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = share_type;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = share_type;
iterator() noexcept = default;
explicit iterator(typename lanes::iterator it) noexcept : it_{it} {}
share_type operator*() const noexcept
{
return share_type::from_raw(static_cast<LaneT>(*it_));
}
share_type operator[](difference_type n) const noexcept
{
return share_type::from_raw(static_cast<LaneT>(it_[n]));
}
iterator & operator++() noexcept { ++it_; return *this; }
iterator operator++(int) noexcept { iterator p = *this; ++*this; return p; }
iterator & operator--() noexcept { --it_; return *this; }
iterator operator--(int) noexcept { iterator p = *this; --*this; return p; }
iterator & operator+=(difference_type n) noexcept { it_ += n; return *this; }
iterator & operator-=(difference_type n) noexcept { it_ -= n; return *this; }
friend iterator operator+(iterator it, difference_type n) noexcept
{
it += n;
return it;
}
friend iterator operator+(difference_type n, iterator it) noexcept
{
return it + n;
}
friend iterator operator-(iterator it, difference_type n) noexcept
{
it -= n;
return it;
}
friend difference_type operator-(iterator a, iterator b) noexcept
{
return a.it_ - b.it_;
}
friend bool operator==(iterator a, iterator b) noexcept { return a.it_ == b.it_; }
friend bool operator!=(iterator a, iterator b) noexcept { return !(a == b); }
friend bool operator<(iterator a, iterator b) noexcept { return a.it_ < b.it_; }
friend bool operator>(iterator a, iterator b) noexcept { return b < a; }
friend bool operator<=(iterator a, iterator b) noexcept { return !(b < a); }
friend bool operator>=(iterator a, iterator b) noexcept { return !(a < b); }
private:
typename lanes::iterator it_{};
};
using const_iterator = iterator;
explicit packed_share_output(size_type nlanes) : lanes(nlanes) {}
packed_share_output(const packed_share_output &) = delete;
packed_share_output & operator=(const packed_share_output &) = delete;
packed_share_output(packed_share_output &&) noexcept = default;
packed_share_output & operator=(packed_share_output &&) noexcept = default;
~packed_share_output() = default;
using lanes::data;
using lanes::empty;
using lanes::size;
reference operator[](size_type i) noexcept
{
return reference{lanes::operator[](i)};
}
share_type operator[](size_type i) const noexcept
{
return share_type::from_raw(lanes::operator[](i));
}
iterator begin() noexcept { return iterator{lanes::begin()}; }
iterator end() noexcept { return iterator{lanes::end()}; }
iterator begin() const noexcept
{
return iterator{typename lanes::iterator{this->data(), 0}};
}
iterator end() const noexcept
{
return iterator{typename lanes::iterator{this->data(), this->size()}};
}
iterator cbegin() const noexcept { return begin(); }
iterator cend() const noexcept { return end(); }
};
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_PACKED_ARRAY_HPP__

View file

@ -0,0 +1,75 @@
/// @file dpf/packed_lane.hpp
/// @brief Extract and deposit one `dpf::bit`, `dpf::twobit`, or `dpf::nyble`
/// lane in a leaf node. Lane 0 is the low bits of the first byte.
#ifndef LIBDPF_INCLUDE_DPF_PACKED_LANE_HPP__
#define LIBDPF_INCLUDE_DPF_PACKED_LANE_HPP__
#include <cassert>
#include <cstddef>
#include <cstdint>
#include "hedley/hedley.h"
#include "dpf/bit.hpp"
#include "dpf/nyble.hpp"
#include "dpf/twobit.hpp"
namespace dpf
{
namespace packed
{
template <typename LaneT, typename LeafT>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
HEDLEY_PURE
LaneT extract_lane(const LeafT & leaf, std::size_t lane) noexcept
{
constexpr unsigned bits = utils::packed_lane_bits_v<LaneT>;
static_assert(bits == 1 || bits == 2 || bits == 4,
"packed lane width must be 1, 2, or 4");
constexpr unsigned mask = (1u << bits) - 1u;
const auto * bytes = reinterpret_cast<const unsigned char *>(
std::addressof(leaf));
const std::size_t bit = lane * bits;
assert(bit / 8u < sizeof(LeafT));
const unsigned shift = static_cast<unsigned>(bit % 8u);
const unsigned val = (bytes[bit / 8u] >> shift) & mask;
return static_cast<LaneT>(val);
}
/// @brief zero `out` is the caller's job; this writes one lane and leaves
/// every other lane untouched.
template <typename LaneT, typename LeafT>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
void deposit_lane(LeafT & leaf, std::size_t lane, LaneT value) noexcept
{
constexpr unsigned bits = utils::packed_lane_bits_v<LaneT>;
static_assert(bits == 1 || bits == 2 || bits == 4,
"packed lane width must be 1, 2, or 4");
constexpr unsigned mask = (1u << bits) - 1u;
auto * bytes = reinterpret_cast<unsigned char *>(std::addressof(leaf));
const std::size_t bit = lane * bits;
assert(bit / 8u < sizeof(LeafT));
const unsigned shift = static_cast<unsigned>(bit % 8u);
const unsigned val = static_cast<unsigned>(value) & mask;
unsigned char & cell = bytes[bit / 8u];
cell = static_cast<unsigned char>((cell & ~(mask << shift)) | (val << shift));
}
template <typename NodeT, typename LaneT>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
NodeT make_lane_node(std::size_t lane, LaneT value) noexcept
{
NodeT node{};
deposit_lane<LaneT>(node, lane, value);
return node;
}
} // namespace packed
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_PACKED_LANE_HPP__

Some files were not shown because too many files have changed in this diff Show more