mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-07-18 23:21:32 -07:00
merge third party folders
This commit is contained in:
@@ -1,330 +0,0 @@
|
||||
This work is released into the public domain with CC0 1.0. Alternatively, it is
|
||||
licensed under the Apache License 2.0.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 Jack O'Connor and Samuel Neves
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,616 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "blake3.h"
|
||||
#include "blake3_impl.h"
|
||||
|
||||
const char *blake3_version(void) { return BLAKE3_VERSION_STRING; }
|
||||
|
||||
INLINE void chunk_state_init(blake3_chunk_state *self, const uint32_t key[8],
|
||||
uint8_t flags) {
|
||||
memcpy(self->cv, key, BLAKE3_KEY_LEN);
|
||||
self->chunk_counter = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
self->buf_len = 0;
|
||||
self->blocks_compressed = 0;
|
||||
self->flags = flags;
|
||||
}
|
||||
|
||||
INLINE void chunk_state_reset(blake3_chunk_state *self, const uint32_t key[8],
|
||||
uint64_t chunk_counter) {
|
||||
memcpy(self->cv, key, BLAKE3_KEY_LEN);
|
||||
self->chunk_counter = chunk_counter;
|
||||
self->blocks_compressed = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
self->buf_len = 0;
|
||||
}
|
||||
|
||||
INLINE size_t chunk_state_len(const blake3_chunk_state *self) {
|
||||
return (BLAKE3_BLOCK_LEN * (size_t)self->blocks_compressed) +
|
||||
((size_t)self->buf_len);
|
||||
}
|
||||
|
||||
INLINE size_t chunk_state_fill_buf(blake3_chunk_state *self,
|
||||
const uint8_t *input, size_t input_len) {
|
||||
size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len);
|
||||
if (take > input_len) {
|
||||
take = input_len;
|
||||
}
|
||||
uint8_t *dest = self->buf + ((size_t)self->buf_len);
|
||||
memcpy(dest, input, take);
|
||||
self->buf_len += (uint8_t)take;
|
||||
return take;
|
||||
}
|
||||
|
||||
INLINE uint8_t chunk_state_maybe_start_flag(const blake3_chunk_state *self) {
|
||||
if (self->blocks_compressed == 0) {
|
||||
return CHUNK_START;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t input_cv[8];
|
||||
uint64_t counter;
|
||||
uint8_t block[BLAKE3_BLOCK_LEN];
|
||||
uint8_t block_len;
|
||||
uint8_t flags;
|
||||
} output_t;
|
||||
|
||||
INLINE output_t make_output(const uint32_t input_cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
output_t ret;
|
||||
memcpy(ret.input_cv, input_cv, 32);
|
||||
memcpy(ret.block, block, BLAKE3_BLOCK_LEN);
|
||||
ret.block_len = block_len;
|
||||
ret.counter = counter;
|
||||
ret.flags = flags;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Chaining values within a given chunk (specifically the compress_in_place
|
||||
// interface) are represented as words. This avoids unnecessary bytes<->words
|
||||
// conversion overhead in the portable implementation. However, the hash_many
|
||||
// interface handles both user input and parent node blocks, so it accepts
|
||||
// bytes. For that reason, chaining values in the CV stack are represented as
|
||||
// bytes.
|
||||
INLINE void output_chaining_value(const output_t *self, uint8_t cv[32]) {
|
||||
uint32_t cv_words[8];
|
||||
memcpy(cv_words, self->input_cv, 32);
|
||||
blake3_compress_in_place(cv_words, self->block, self->block_len,
|
||||
self->counter, self->flags);
|
||||
store_cv_words(cv, cv_words);
|
||||
}
|
||||
|
||||
INLINE void output_root_bytes(const output_t *self, uint64_t seek, uint8_t *out,
|
||||
size_t out_len) {
|
||||
uint64_t output_block_counter = seek / 64;
|
||||
size_t offset_within_block = seek % 64;
|
||||
uint8_t wide_buf[64];
|
||||
while (out_len > 0) {
|
||||
blake3_compress_xof(self->input_cv, self->block, self->block_len,
|
||||
output_block_counter, self->flags | ROOT, wide_buf);
|
||||
size_t available_bytes = 64 - offset_within_block;
|
||||
size_t memcpy_len;
|
||||
if (out_len > available_bytes) {
|
||||
memcpy_len = available_bytes;
|
||||
} else {
|
||||
memcpy_len = out_len;
|
||||
}
|
||||
memcpy(out, wide_buf + offset_within_block, memcpy_len);
|
||||
out += memcpy_len;
|
||||
out_len -= memcpy_len;
|
||||
output_block_counter += 1;
|
||||
offset_within_block = 0;
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void chunk_state_update(blake3_chunk_state *self, const uint8_t *input,
|
||||
size_t input_len) {
|
||||
if (self->buf_len > 0) {
|
||||
size_t take = chunk_state_fill_buf(self, input, input_len);
|
||||
input += take;
|
||||
input_len -= take;
|
||||
if (input_len > 0) {
|
||||
blake3_compress_in_place(
|
||||
self->cv, self->buf, BLAKE3_BLOCK_LEN, self->chunk_counter,
|
||||
self->flags | chunk_state_maybe_start_flag(self));
|
||||
self->blocks_compressed += 1;
|
||||
self->buf_len = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
while (input_len > BLAKE3_BLOCK_LEN) {
|
||||
blake3_compress_in_place(self->cv, input, BLAKE3_BLOCK_LEN,
|
||||
self->chunk_counter,
|
||||
self->flags | chunk_state_maybe_start_flag(self));
|
||||
self->blocks_compressed += 1;
|
||||
input += BLAKE3_BLOCK_LEN;
|
||||
input_len -= BLAKE3_BLOCK_LEN;
|
||||
}
|
||||
|
||||
size_t take = chunk_state_fill_buf(self, input, input_len);
|
||||
input += take;
|
||||
input_len -= take;
|
||||
}
|
||||
|
||||
INLINE output_t chunk_state_output(const blake3_chunk_state *self) {
|
||||
uint8_t block_flags =
|
||||
self->flags | chunk_state_maybe_start_flag(self) | CHUNK_END;
|
||||
return make_output(self->cv, self->buf, self->buf_len, self->chunk_counter,
|
||||
block_flags);
|
||||
}
|
||||
|
||||
INLINE output_t parent_output(const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
const uint32_t key[8], uint8_t flags) {
|
||||
return make_output(key, block, BLAKE3_BLOCK_LEN, 0, flags | PARENT);
|
||||
}
|
||||
|
||||
// Given some input larger than one chunk, return the number of bytes that
|
||||
// should go in the left subtree. This is the largest power-of-2 number of
|
||||
// chunks that leaves at least 1 byte for the right subtree.
|
||||
INLINE size_t left_len(size_t content_len) {
|
||||
// Subtract 1 to reserve at least one byte for the right side. content_len
|
||||
// should always be greater than BLAKE3_CHUNK_LEN.
|
||||
size_t full_chunks = (content_len - 1) / BLAKE3_CHUNK_LEN;
|
||||
return round_down_to_power_of_2(full_chunks) * BLAKE3_CHUNK_LEN;
|
||||
}
|
||||
|
||||
// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE chunks at the same time
|
||||
// on a single thread. Write out the chunk chaining values and return the
|
||||
// number of chunks hashed. These chunks are never the root and never empty;
|
||||
// those cases use a different codepath.
|
||||
INLINE size_t compress_chunks_parallel(const uint8_t *input, size_t input_len,
|
||||
const uint32_t key[8],
|
||||
uint64_t chunk_counter, uint8_t flags,
|
||||
uint8_t *out) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(0 < input_len);
|
||||
assert(input_len <= MAX_SIMD_DEGREE * BLAKE3_CHUNK_LEN);
|
||||
#endif
|
||||
|
||||
const uint8_t *chunks_array[MAX_SIMD_DEGREE];
|
||||
size_t input_position = 0;
|
||||
size_t chunks_array_len = 0;
|
||||
while (input_len - input_position >= BLAKE3_CHUNK_LEN) {
|
||||
chunks_array[chunks_array_len] = &input[input_position];
|
||||
input_position += BLAKE3_CHUNK_LEN;
|
||||
chunks_array_len += 1;
|
||||
}
|
||||
|
||||
blake3_hash_many(chunks_array, chunks_array_len,
|
||||
BLAKE3_CHUNK_LEN / BLAKE3_BLOCK_LEN, key, chunk_counter,
|
||||
true, flags, CHUNK_START, CHUNK_END, out);
|
||||
|
||||
// Hash the remaining partial chunk, if there is one. Note that the empty
|
||||
// chunk (meaning the empty message) is a different codepath.
|
||||
if (input_len > input_position) {
|
||||
uint64_t counter = chunk_counter + (uint64_t)chunks_array_len;
|
||||
blake3_chunk_state chunk_state;
|
||||
chunk_state_init(&chunk_state, key, flags);
|
||||
chunk_state.chunk_counter = counter;
|
||||
chunk_state_update(&chunk_state, &input[input_position],
|
||||
input_len - input_position);
|
||||
output_t output = chunk_state_output(&chunk_state);
|
||||
output_chaining_value(&output, &out[chunks_array_len * BLAKE3_OUT_LEN]);
|
||||
return chunks_array_len + 1;
|
||||
} else {
|
||||
return chunks_array_len;
|
||||
}
|
||||
}
|
||||
|
||||
// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE parents at the same time
|
||||
// on a single thread. Write out the parent chaining values and return the
|
||||
// number of parents hashed. (If there's an odd input chaining value left over,
|
||||
// return it as an additional output.) These parents are never the root and
|
||||
// never empty; those cases use a different codepath.
|
||||
INLINE size_t compress_parents_parallel(const uint8_t *child_chaining_values,
|
||||
size_t num_chaining_values,
|
||||
const uint32_t key[8], uint8_t flags,
|
||||
uint8_t *out) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(2 <= num_chaining_values);
|
||||
assert(num_chaining_values <= 2 * MAX_SIMD_DEGREE_OR_2);
|
||||
#endif
|
||||
|
||||
const uint8_t *parents_array[MAX_SIMD_DEGREE_OR_2];
|
||||
size_t parents_array_len = 0;
|
||||
while (num_chaining_values - (2 * parents_array_len) >= 2) {
|
||||
parents_array[parents_array_len] =
|
||||
&child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN];
|
||||
parents_array_len += 1;
|
||||
}
|
||||
|
||||
blake3_hash_many(parents_array, parents_array_len, 1, key,
|
||||
0, // Parents always use counter 0.
|
||||
false, flags | PARENT,
|
||||
0, // Parents have no start flags.
|
||||
0, // Parents have no end flags.
|
||||
out);
|
||||
|
||||
// If there's an odd child left over, it becomes an output.
|
||||
if (num_chaining_values > 2 * parents_array_len) {
|
||||
memcpy(&out[parents_array_len * BLAKE3_OUT_LEN],
|
||||
&child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN],
|
||||
BLAKE3_OUT_LEN);
|
||||
return parents_array_len + 1;
|
||||
} else {
|
||||
return parents_array_len;
|
||||
}
|
||||
}
|
||||
|
||||
// The wide helper function returns (writes out) an array of chaining values
|
||||
// and returns the length of that array. The number of chaining values returned
|
||||
// is the dynamically detected SIMD degree, at most MAX_SIMD_DEGREE. Or fewer,
|
||||
// if the input is shorter than that many chunks. The reason for maintaining a
|
||||
// wide array of chaining values going back up the tree, is to allow the
|
||||
// implementation to hash as many parents in parallel as possible.
|
||||
//
|
||||
// As a special case when the SIMD degree is 1, this function will still return
|
||||
// at least 2 outputs. This guarantees that this function doesn't perform the
|
||||
// root compression. (If it did, it would use the wrong flags, and also we
|
||||
// wouldn't be able to implement extendable output.) Note that this function is
|
||||
// not used when the whole input is only 1 chunk long; that's a different
|
||||
// codepath.
|
||||
//
|
||||
// Why not just have the caller split the input on the first update(), instead
|
||||
// of implementing this special rule? Because we don't want to limit SIMD or
|
||||
// multi-threading parallelism for that update().
|
||||
static size_t blake3_compress_subtree_wide(const uint8_t *input,
|
||||
size_t input_len,
|
||||
const uint32_t key[8],
|
||||
uint64_t chunk_counter,
|
||||
uint8_t flags, uint8_t *out) {
|
||||
// Note that the single chunk case does *not* bump the SIMD degree up to 2
|
||||
// when it is 1. If this implementation adds multi-threading in the future,
|
||||
// this gives us the option of multi-threading even the 2-chunk case, which
|
||||
// can help performance on smaller platforms.
|
||||
if (input_len <= blake3_simd_degree() * BLAKE3_CHUNK_LEN) {
|
||||
return compress_chunks_parallel(input, input_len, key, chunk_counter, flags,
|
||||
out);
|
||||
}
|
||||
|
||||
// With more than simd_degree chunks, we need to recurse. Start by dividing
|
||||
// the input into left and right subtrees. (Note that this is only optimal
|
||||
// as long as the SIMD degree is a power of 2. If we ever get a SIMD degree
|
||||
// of 3 or something, we'll need a more complicated strategy.)
|
||||
size_t left_input_len = left_len(input_len);
|
||||
size_t right_input_len = input_len - left_input_len;
|
||||
const uint8_t *right_input = &input[left_input_len];
|
||||
uint64_t right_chunk_counter =
|
||||
chunk_counter + (uint64_t)(left_input_len / BLAKE3_CHUNK_LEN);
|
||||
|
||||
// Make space for the child outputs. Here we use MAX_SIMD_DEGREE_OR_2 to
|
||||
// account for the special case of returning 2 outputs when the SIMD degree
|
||||
// is 1.
|
||||
uint8_t cv_array[2 * MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];
|
||||
size_t degree = blake3_simd_degree();
|
||||
if (left_input_len > BLAKE3_CHUNK_LEN && degree == 1) {
|
||||
// The special case: We always use a degree of at least two, to make
|
||||
// sure there are two outputs. Except, as noted above, at the chunk
|
||||
// level, where we allow degree=1. (Note that the 1-chunk-input case is
|
||||
// a different codepath.)
|
||||
degree = 2;
|
||||
}
|
||||
uint8_t *right_cvs = &cv_array[degree * BLAKE3_OUT_LEN];
|
||||
|
||||
// Recurse! If this implementation adds multi-threading support in the
|
||||
// future, this is where it will go.
|
||||
size_t left_n = blake3_compress_subtree_wide(input, left_input_len, key,
|
||||
chunk_counter, flags, cv_array);
|
||||
size_t right_n = blake3_compress_subtree_wide(
|
||||
right_input, right_input_len, key, right_chunk_counter, flags, right_cvs);
|
||||
|
||||
// The special case again. If simd_degree=1, then we'll have left_n=1 and
|
||||
// right_n=1. Rather than compressing them into a single output, return
|
||||
// them directly, to make sure we always have at least two outputs.
|
||||
if (left_n == 1) {
|
||||
memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Otherwise, do one layer of parent node compression.
|
||||
size_t num_chaining_values = left_n + right_n;
|
||||
return compress_parents_parallel(cv_array, num_chaining_values, key, flags,
|
||||
out);
|
||||
}
|
||||
|
||||
// Hash a subtree with compress_subtree_wide(), and then condense the resulting
|
||||
// list of chaining values down to a single parent node. Don't compress that
|
||||
// last parent node, however. Instead, return its message bytes (the
|
||||
// concatenated chaining values of its children). This is necessary when the
|
||||
// first call to update() supplies a complete subtree, because the topmost
|
||||
// parent node of that subtree could end up being the root. It's also necessary
|
||||
// for extended output in the general case.
|
||||
//
|
||||
// As with compress_subtree_wide(), this function is not used on inputs of 1
|
||||
// chunk or less. That's a different codepath.
|
||||
INLINE void compress_subtree_to_parent_node(
|
||||
const uint8_t *input, size_t input_len, const uint32_t key[8],
|
||||
uint64_t chunk_counter, uint8_t flags, uint8_t out[2 * BLAKE3_OUT_LEN]) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(input_len > BLAKE3_CHUNK_LEN);
|
||||
#endif
|
||||
|
||||
uint8_t cv_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];
|
||||
size_t num_cvs = blake3_compress_subtree_wide(input, input_len, key,
|
||||
chunk_counter, flags, cv_array);
|
||||
assert(num_cvs <= MAX_SIMD_DEGREE_OR_2);
|
||||
|
||||
// If MAX_SIMD_DEGREE is greater than 2 and there's enough input,
|
||||
// compress_subtree_wide() returns more than 2 chaining values. Condense
|
||||
// them into 2 by forming parent nodes repeatedly.
|
||||
uint8_t out_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN / 2];
|
||||
// The second half of this loop condition is always true, and we just
|
||||
// asserted it above. But GCC can't tell that it's always true, and if NDEBUG
|
||||
// is set on platforms where MAX_SIMD_DEGREE_OR_2 == 2, GCC emits spurious
|
||||
// warnings here. GCC 8.5 is particularly sensitive, so if you're changing
|
||||
// this code, test it against that version.
|
||||
while (num_cvs > 2 && num_cvs <= MAX_SIMD_DEGREE_OR_2) {
|
||||
num_cvs =
|
||||
compress_parents_parallel(cv_array, num_cvs, key, flags, out_array);
|
||||
memcpy(cv_array, out_array, num_cvs * BLAKE3_OUT_LEN);
|
||||
}
|
||||
memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN);
|
||||
}
|
||||
|
||||
INLINE void hasher_init_base(blake3_hasher *self, const uint32_t key[8],
|
||||
uint8_t flags) {
|
||||
memcpy(self->key, key, BLAKE3_KEY_LEN);
|
||||
chunk_state_init(&self->chunk, key, flags);
|
||||
self->cv_stack_len = 0;
|
||||
}
|
||||
|
||||
void blake3_hasher_init(blake3_hasher *self) { hasher_init_base(self, IV, 0); }
|
||||
|
||||
void blake3_hasher_init_keyed(blake3_hasher *self,
|
||||
const uint8_t key[BLAKE3_KEY_LEN]) {
|
||||
uint32_t key_words[8];
|
||||
load_key_words(key, key_words);
|
||||
hasher_init_base(self, key_words, KEYED_HASH);
|
||||
}
|
||||
|
||||
void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
|
||||
size_t context_len) {
|
||||
blake3_hasher context_hasher;
|
||||
hasher_init_base(&context_hasher, IV, DERIVE_KEY_CONTEXT);
|
||||
blake3_hasher_update(&context_hasher, context, context_len);
|
||||
uint8_t context_key[BLAKE3_KEY_LEN];
|
||||
blake3_hasher_finalize(&context_hasher, context_key, BLAKE3_KEY_LEN);
|
||||
uint32_t context_key_words[8];
|
||||
load_key_words(context_key, context_key_words);
|
||||
hasher_init_base(self, context_key_words, DERIVE_KEY_MATERIAL);
|
||||
}
|
||||
|
||||
void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context) {
|
||||
blake3_hasher_init_derive_key_raw(self, context, strlen(context));
|
||||
}
|
||||
|
||||
// As described in hasher_push_cv() below, we do "lazy merging", delaying
|
||||
// merges until right before the next CV is about to be added. This is
|
||||
// different from the reference implementation. Another difference is that we
|
||||
// aren't always merging 1 chunk at a time. Instead, each CV might represent
|
||||
// any power-of-two number of chunks, as long as the smaller-above-larger stack
|
||||
// order is maintained. Instead of the "count the trailing 0-bits" algorithm
|
||||
// described in the spec, we use a "count the total number of 1-bits" variant
|
||||
// that doesn't require us to retain the subtree size of the CV on top of the
|
||||
// stack. The principle is the same: each CV that should remain in the stack is
|
||||
// represented by a 1-bit in the total number of chunks (or bytes) so far.
|
||||
INLINE void hasher_merge_cv_stack(blake3_hasher *self, uint64_t total_len) {
|
||||
size_t post_merge_stack_len = (size_t)popcnt(total_len);
|
||||
while (self->cv_stack_len > post_merge_stack_len) {
|
||||
uint8_t *parent_node =
|
||||
&self->cv_stack[(self->cv_stack_len - 2) * BLAKE3_OUT_LEN];
|
||||
output_t output = parent_output(parent_node, self->key, self->chunk.flags);
|
||||
output_chaining_value(&output, parent_node);
|
||||
self->cv_stack_len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// In reference_impl.rs, we merge the new CV with existing CVs from the stack
|
||||
// before pushing it. We can do that because we know more input is coming, so
|
||||
// we know none of the merges are root.
|
||||
//
|
||||
// This setting is different. We want to feed as much input as possible to
|
||||
// compress_subtree_wide(), without setting aside anything for the chunk_state.
|
||||
// If the user gives us 64 KiB, we want to parallelize over all 64 KiB at once
|
||||
// as a single subtree, if at all possible.
|
||||
//
|
||||
// This leads to two problems:
|
||||
// 1) This 64 KiB input might be the only call that ever gets made to update.
|
||||
// In this case, the root node of the 64 KiB subtree would be the root node
|
||||
// of the whole tree, and it would need to be ROOT finalized. We can't
|
||||
// compress it until we know.
|
||||
// 2) This 64 KiB input might complete a larger tree, whose root node is
|
||||
// similarly going to be the the root of the whole tree. For example, maybe
|
||||
// we have 196 KiB (that is, 128 + 64) hashed so far. We can't compress the
|
||||
// node at the root of the 256 KiB subtree until we know how to finalize it.
|
||||
//
|
||||
// The second problem is solved with "lazy merging". That is, when we're about
|
||||
// to add a CV to the stack, we don't merge it with anything first, as the
|
||||
// reference impl does. Instead we do merges using the *previous* CV that was
|
||||
// added, which is sitting on top of the stack, and we put the new CV
|
||||
// (unmerged) on top of the stack afterwards. This guarantees that we never
|
||||
// merge the root node until finalize().
|
||||
//
|
||||
// Solving the first problem requires an additional tool,
|
||||
// compress_subtree_to_parent_node(). That function always returns the top
|
||||
// *two* chaining values of the subtree it's compressing. We then do lazy
|
||||
// merging with each of them separately, so that the second CV will always
|
||||
// remain unmerged. (That also helps us support extendable output when we're
|
||||
// hashing an input all-at-once.)
|
||||
INLINE void hasher_push_cv(blake3_hasher *self, uint8_t new_cv[BLAKE3_OUT_LEN],
|
||||
uint64_t chunk_counter) {
|
||||
hasher_merge_cv_stack(self, chunk_counter);
|
||||
memcpy(&self->cv_stack[self->cv_stack_len * BLAKE3_OUT_LEN], new_cv,
|
||||
BLAKE3_OUT_LEN);
|
||||
self->cv_stack_len += 1;
|
||||
}
|
||||
|
||||
void blake3_hasher_update(blake3_hasher *self, const void *input,
|
||||
size_t input_len) {
|
||||
// Explicitly checking for zero avoids causing UB by passing a null pointer
|
||||
// to memcpy. This comes up in practice with things like:
|
||||
// std::vector<uint8_t> v;
|
||||
// blake3_hasher_update(&hasher, v.data(), v.size());
|
||||
if (input_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *input_bytes = (const uint8_t *)input;
|
||||
|
||||
// If we have some partial chunk bytes in the internal chunk_state, we need
|
||||
// to finish that chunk first.
|
||||
if (chunk_state_len(&self->chunk) > 0) {
|
||||
size_t take = BLAKE3_CHUNK_LEN - chunk_state_len(&self->chunk);
|
||||
if (take > input_len) {
|
||||
take = input_len;
|
||||
}
|
||||
chunk_state_update(&self->chunk, input_bytes, take);
|
||||
input_bytes += take;
|
||||
input_len -= take;
|
||||
// If we've filled the current chunk and there's more coming, finalize this
|
||||
// chunk and proceed. In this case we know it's not the root.
|
||||
if (input_len > 0) {
|
||||
output_t output = chunk_state_output(&self->chunk);
|
||||
uint8_t chunk_cv[32];
|
||||
output_chaining_value(&output, chunk_cv);
|
||||
hasher_push_cv(self, chunk_cv, self->chunk.chunk_counter);
|
||||
chunk_state_reset(&self->chunk, self->key, self->chunk.chunk_counter + 1);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now the chunk_state is clear, and we have more input. If there's more than
|
||||
// a single chunk (so, definitely not the root chunk), hash the largest whole
|
||||
// subtree we can, with the full benefits of SIMD (and maybe in the future,
|
||||
// multi-threading) parallelism. Two restrictions:
|
||||
// - The subtree has to be a power-of-2 number of chunks. Only subtrees along
|
||||
// the right edge can be incomplete, and we don't know where the right edge
|
||||
// is going to be until we get to finalize().
|
||||
// - The subtree must evenly divide the total number of chunks up until this
|
||||
// point (if total is not 0). If the current incomplete subtree is only
|
||||
// waiting for 1 more chunk, we can't hash a subtree of 4 chunks. We have
|
||||
// to complete the current subtree first.
|
||||
// Because we might need to break up the input to form powers of 2, or to
|
||||
// evenly divide what we already have, this part runs in a loop.
|
||||
while (input_len > BLAKE3_CHUNK_LEN) {
|
||||
size_t subtree_len = round_down_to_power_of_2(input_len);
|
||||
uint64_t count_so_far = self->chunk.chunk_counter * BLAKE3_CHUNK_LEN;
|
||||
// Shrink the subtree_len until it evenly divides the count so far. We know
|
||||
// that subtree_len itself is a power of 2, so we can use a bitmasking
|
||||
// trick instead of an actual remainder operation. (Note that if the caller
|
||||
// consistently passes power-of-2 inputs of the same size, as is hopefully
|
||||
// typical, this loop condition will always fail, and subtree_len will
|
||||
// always be the full length of the input.)
|
||||
//
|
||||
// An aside: We don't have to shrink subtree_len quite this much. For
|
||||
// example, if count_so_far is 1, we could pass 2 chunks to
|
||||
// compress_subtree_to_parent_node. Since we'll get 2 CVs back, we'll still
|
||||
// get the right answer in the end, and we might get to use 2-way SIMD
|
||||
// parallelism. The problem with this optimization, is that it gets us
|
||||
// stuck always hashing 2 chunks. The total number of chunks will remain
|
||||
// odd, and we'll never graduate to higher degrees of parallelism. See
|
||||
// https://github.com/BLAKE3-team/BLAKE3/issues/69.
|
||||
while ((((uint64_t)(subtree_len - 1)) & count_so_far) != 0) {
|
||||
subtree_len /= 2;
|
||||
}
|
||||
// The shrunken subtree_len might now be 1 chunk long. If so, hash that one
|
||||
// chunk by itself. Otherwise, compress the subtree into a pair of CVs.
|
||||
uint64_t subtree_chunks = subtree_len / BLAKE3_CHUNK_LEN;
|
||||
if (subtree_len <= BLAKE3_CHUNK_LEN) {
|
||||
blake3_chunk_state chunk_state;
|
||||
chunk_state_init(&chunk_state, self->key, self->chunk.flags);
|
||||
chunk_state.chunk_counter = self->chunk.chunk_counter;
|
||||
chunk_state_update(&chunk_state, input_bytes, subtree_len);
|
||||
output_t output = chunk_state_output(&chunk_state);
|
||||
uint8_t cv[BLAKE3_OUT_LEN];
|
||||
output_chaining_value(&output, cv);
|
||||
hasher_push_cv(self, cv, chunk_state.chunk_counter);
|
||||
} else {
|
||||
// This is the high-performance happy path, though getting here depends
|
||||
// on the caller giving us a long enough input.
|
||||
uint8_t cv_pair[2 * BLAKE3_OUT_LEN];
|
||||
compress_subtree_to_parent_node(input_bytes, subtree_len, self->key,
|
||||
self->chunk.chunk_counter,
|
||||
self->chunk.flags, cv_pair);
|
||||
hasher_push_cv(self, cv_pair, self->chunk.chunk_counter);
|
||||
hasher_push_cv(self, &cv_pair[BLAKE3_OUT_LEN],
|
||||
self->chunk.chunk_counter + (subtree_chunks / 2));
|
||||
}
|
||||
self->chunk.chunk_counter += subtree_chunks;
|
||||
input_bytes += subtree_len;
|
||||
input_len -= subtree_len;
|
||||
}
|
||||
|
||||
// If there's any remaining input less than a full chunk, add it to the chunk
|
||||
// state. In that case, also do a final merge loop to make sure the subtree
|
||||
// stack doesn't contain any unmerged pairs. The remaining input means we
|
||||
// know these merges are non-root. This merge loop isn't strictly necessary
|
||||
// here, because hasher_push_chunk_cv already does its own merge loop, but it
|
||||
// simplifies blake3_hasher_finalize below.
|
||||
if (input_len > 0) {
|
||||
chunk_state_update(&self->chunk, input_bytes, input_len);
|
||||
hasher_merge_cv_stack(self, self->chunk.chunk_counter);
|
||||
}
|
||||
}
|
||||
|
||||
void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,
|
||||
size_t out_len) {
|
||||
blake3_hasher_finalize_seek(self, 0, out, out_len);
|
||||
}
|
||||
|
||||
void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
|
||||
uint8_t *out, size_t out_len) {
|
||||
// Explicitly checking for zero avoids causing UB by passing a null pointer
|
||||
// to memcpy. This comes up in practice with things like:
|
||||
// std::vector<uint8_t> v;
|
||||
// blake3_hasher_finalize(&hasher, v.data(), v.size());
|
||||
if (out_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the subtree stack is empty, then the current chunk is the root.
|
||||
if (self->cv_stack_len == 0) {
|
||||
output_t output = chunk_state_output(&self->chunk);
|
||||
output_root_bytes(&output, seek, out, out_len);
|
||||
return;
|
||||
}
|
||||
// If there are any bytes in the chunk state, finalize that chunk and do a
|
||||
// roll-up merge between that chunk hash and every subtree in the stack. In
|
||||
// this case, the extra merge loop at the end of blake3_hasher_update
|
||||
// guarantees that none of the subtrees in the stack need to be merged with
|
||||
// each other first. Otherwise, if there are no bytes in the chunk state,
|
||||
// then the top of the stack is a chunk hash, and we start the merge from
|
||||
// that.
|
||||
output_t output;
|
||||
size_t cvs_remaining;
|
||||
if (chunk_state_len(&self->chunk) > 0) {
|
||||
cvs_remaining = self->cv_stack_len;
|
||||
output = chunk_state_output(&self->chunk);
|
||||
} else {
|
||||
// There are always at least 2 CVs in the stack in this case.
|
||||
cvs_remaining = self->cv_stack_len - 2;
|
||||
output = parent_output(&self->cv_stack[cvs_remaining * 32], self->key,
|
||||
self->chunk.flags);
|
||||
}
|
||||
while (cvs_remaining > 0) {
|
||||
cvs_remaining -= 1;
|
||||
uint8_t parent_block[BLAKE3_BLOCK_LEN];
|
||||
memcpy(parent_block, &self->cv_stack[cvs_remaining * 32], 32);
|
||||
output_chaining_value(&output, &parent_block[32]);
|
||||
output = parent_output(parent_block, self->key, self->chunk.flags);
|
||||
}
|
||||
output_root_bytes(&output, seek, out, out_len);
|
||||
}
|
||||
|
||||
void blake3_hasher_reset(blake3_hasher *self) {
|
||||
chunk_state_reset(&self->chunk, self->key, 0);
|
||||
self->cv_stack_len = 0;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#ifndef BLAKE3_H
|
||||
#define BLAKE3_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if !defined(BLAKE3_API)
|
||||
# if defined(_WIN32) || defined(__CYGWIN__)
|
||||
# if defined(BLAKE3_DLL)
|
||||
# if defined(BLAKE3_DLL_EXPORTS)
|
||||
# define BLAKE3_API __declspec(dllexport)
|
||||
# else
|
||||
# define BLAKE3_API __declspec(dllimport)
|
||||
# endif
|
||||
# define BLAKE3_PRIVATE
|
||||
# else
|
||||
# define BLAKE3_API
|
||||
# define BLAKE3_PRIVATE
|
||||
# endif
|
||||
# elif __GNUC__ >= 4
|
||||
# define BLAKE3_API __attribute__((visibility("default")))
|
||||
# define BLAKE3_PRIVATE __attribute__((visibility("hidden")))
|
||||
# else
|
||||
# define BLAKE3_API
|
||||
# define BLAKE3_PRIVATE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BLAKE3_VERSION_STRING "1.5.0"
|
||||
#define BLAKE3_KEY_LEN 32
|
||||
#define BLAKE3_OUT_LEN 32
|
||||
#define BLAKE3_BLOCK_LEN 64
|
||||
#define BLAKE3_CHUNK_LEN 1024
|
||||
#define BLAKE3_MAX_DEPTH 54
|
||||
|
||||
// This struct is a private implementation detail. It has to be here because
|
||||
// it's part of blake3_hasher below.
|
||||
typedef struct {
|
||||
uint32_t cv[8];
|
||||
uint64_t chunk_counter;
|
||||
uint8_t buf[BLAKE3_BLOCK_LEN];
|
||||
uint8_t buf_len;
|
||||
uint8_t blocks_compressed;
|
||||
uint8_t flags;
|
||||
} blake3_chunk_state;
|
||||
|
||||
typedef struct {
|
||||
uint32_t key[8];
|
||||
blake3_chunk_state chunk;
|
||||
uint8_t cv_stack_len;
|
||||
// The stack size is MAX_DEPTH + 1 because we do lazy merging. For example,
|
||||
// with 7 chunks, we have 3 entries in the stack. Adding an 8th chunk
|
||||
// requires a 4th entry, rather than merging everything down to 1, because we
|
||||
// don't know whether more input is coming. This is different from how the
|
||||
// reference implementation does things.
|
||||
uint8_t cv_stack[(BLAKE3_MAX_DEPTH + 1) * BLAKE3_OUT_LEN];
|
||||
} blake3_hasher;
|
||||
|
||||
BLAKE3_API const char *blake3_version(void);
|
||||
BLAKE3_API void blake3_hasher_init(blake3_hasher *self);
|
||||
BLAKE3_API void blake3_hasher_init_keyed(blake3_hasher *self,
|
||||
const uint8_t key[BLAKE3_KEY_LEN]);
|
||||
BLAKE3_API void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context);
|
||||
BLAKE3_API void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
|
||||
size_t context_len);
|
||||
BLAKE3_API void blake3_hasher_update(blake3_hasher *self, const void *input,
|
||||
size_t input_len);
|
||||
BLAKE3_API void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,
|
||||
size_t out_len);
|
||||
BLAKE3_API void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
|
||||
uint8_t *out, size_t out_len);
|
||||
BLAKE3_API void blake3_hasher_reset(blake3_hasher *self);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLAKE3_H */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,278 +0,0 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "blake3_impl.h"
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#elif defined(__GNUC__)
|
||||
#include <immintrin.h>
|
||||
#else
|
||||
#undef IS_X86 /* Unimplemented! */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAYBE_UNUSED(x) (void)((x))
|
||||
|
||||
#if defined(IS_X86)
|
||||
static uint64_t xgetbv(void) {
|
||||
#if defined(_MSC_VER)
|
||||
return _xgetbv(0);
|
||||
#else
|
||||
uint32_t eax = 0, edx = 0;
|
||||
__asm__ __volatile__("xgetbv\n" : "=a"(eax), "=d"(edx) : "c"(0));
|
||||
return ((uint64_t)edx << 32) | eax;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cpuid(uint32_t out[4], uint32_t id) {
|
||||
#if defined(_MSC_VER)
|
||||
__cpuid((int *)out, id);
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
__asm__ __volatile__("movl %%ebx, %1\n"
|
||||
"cpuid\n"
|
||||
"xchgl %1, %%ebx\n"
|
||||
: "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id));
|
||||
#else
|
||||
__asm__ __volatile__("cpuid\n"
|
||||
: "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cpuidex(uint32_t out[4], uint32_t id, uint32_t sid) {
|
||||
#if defined(_MSC_VER)
|
||||
__cpuidex((int *)out, id, sid);
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
__asm__ __volatile__("movl %%ebx, %1\n"
|
||||
"cpuid\n"
|
||||
"xchgl %1, %%ebx\n"
|
||||
: "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id), "c"(sid));
|
||||
#else
|
||||
__asm__ __volatile__("cpuid\n"
|
||||
: "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id), "c"(sid));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
enum cpu_feature {
|
||||
SSE2 = 1 << 0,
|
||||
SSSE3 = 1 << 1,
|
||||
SSE41 = 1 << 2,
|
||||
AVX = 1 << 3,
|
||||
AVX2 = 1 << 4,
|
||||
AVX512F = 1 << 5,
|
||||
AVX512VL = 1 << 6,
|
||||
/* ... */
|
||||
UNDEFINED = 1 << 30
|
||||
};
|
||||
|
||||
#if !defined(BLAKE3_TESTING)
|
||||
static /* Allow the variable to be controlled manually for testing */
|
||||
#endif
|
||||
volatile int g_cpu_features = UNDEFINED;
|
||||
|
||||
#if !defined(BLAKE3_TESTING)
|
||||
static
|
||||
#endif
|
||||
enum cpu_feature
|
||||
get_cpu_features(void) {
|
||||
|
||||
/* If TSAN detects a data race here, try compiling with -DBLAKE3_ATOMICS=1 */
|
||||
long features = g_cpu_features;
|
||||
if (features != UNDEFINED) {
|
||||
return (enum cpu_feature)features;
|
||||
} else {
|
||||
#if defined(IS_X86)
|
||||
uint32_t regs[4] = {0};
|
||||
uint32_t *eax = ®s[0], *ebx = ®s[1], *ecx = ®s[2], *edx = ®s[3];
|
||||
(void)edx;
|
||||
features = 0;
|
||||
cpuid(regs, 0);
|
||||
const int max_id = *eax;
|
||||
cpuid(regs, 1);
|
||||
#if defined(__amd64__) || defined(_M_X64)
|
||||
features |= SSE2;
|
||||
#else
|
||||
if (*edx & (1UL << 26))
|
||||
features |= SSE2;
|
||||
#endif
|
||||
if (*ecx & (1UL << 9))
|
||||
features |= SSSE3;
|
||||
if (*ecx & (1UL << 19))
|
||||
features |= SSE41;
|
||||
|
||||
if (*ecx & (1UL << 27)) { // OSXSAVE
|
||||
const uint64_t mask = xgetbv();
|
||||
if ((mask & 6) == 6) { // SSE and AVX states
|
||||
if (*ecx & (1UL << 28))
|
||||
features |= AVX;
|
||||
if (max_id >= 7) {
|
||||
cpuidex(regs, 7, 0);
|
||||
if (*ebx & (1UL << 5))
|
||||
features |= AVX2;
|
||||
if ((mask & 224) == 224) { // Opmask, ZMM_Hi256, Hi16_Zmm
|
||||
if (*ebx & (1UL << 31))
|
||||
features |= AVX512VL;
|
||||
if (*ebx & (1UL << 16))
|
||||
features |= AVX512F;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
g_cpu_features = features;
|
||||
return (enum cpu_feature)features;
|
||||
#else
|
||||
/* How to detect NEON? */
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void blake3_compress_in_place(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if (features & AVX512VL) {
|
||||
blake3_compress_in_place_avx512(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_compress_in_place_sse41(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_compress_in_place_sse2(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
blake3_compress_in_place_portable(cv, block, block_len, counter, flags);
|
||||
}
|
||||
|
||||
void blake3_compress_xof(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t out[64]) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if (features & AVX512VL) {
|
||||
blake3_compress_xof_avx512(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_compress_xof_sse41(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_compress_xof_sse2(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
blake3_compress_xof_portable(cv, block, block_len, counter, flags, out);
|
||||
}
|
||||
|
||||
void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) {
|
||||
blake3_hash_many_avx512(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
if (features & AVX2) {
|
||||
blake3_hash_many_avx2(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_hash_many_sse41(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_hash_many_sse2(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
blake3_hash_many_neon(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end, out);
|
||||
return;
|
||||
#endif
|
||||
|
||||
blake3_hash_many_portable(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
}
|
||||
|
||||
// The dynamically detected SIMD degree of the current platform.
|
||||
size_t blake3_simd_degree(void) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) {
|
||||
return 16;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
if (features & AVX2) {
|
||||
return 8;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
return 4;
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
#ifndef BLAKE3_IMPL_H
|
||||
#define BLAKE3_IMPL_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "blake3.h"
|
||||
|
||||
// internal flags
|
||||
enum blake3_flags {
|
||||
CHUNK_START = 1 << 0,
|
||||
CHUNK_END = 1 << 1,
|
||||
PARENT = 1 << 2,
|
||||
ROOT = 1 << 3,
|
||||
KEYED_HASH = 1 << 4,
|
||||
DERIVE_KEY_CONTEXT = 1 << 5,
|
||||
DERIVE_KEY_MATERIAL = 1 << 6,
|
||||
};
|
||||
|
||||
// This C implementation tries to support recent versions of GCC, Clang, and
|
||||
// MSVC.
|
||||
#if defined(_MSC_VER)
|
||||
#define INLINE static __forceinline
|
||||
#else
|
||||
#define INLINE static inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define IS_X86
|
||||
#define IS_X86_64
|
||||
#endif
|
||||
|
||||
#if defined(__i386__) || defined(_M_IX86)
|
||||
#define IS_X86
|
||||
#define IS_X86_32
|
||||
#endif
|
||||
|
||||
#if defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define IS_AARCH64
|
||||
#endif
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BLAKE3_USE_NEON)
|
||||
// If BLAKE3_USE_NEON not manually set, autodetect based on AArch64ness
|
||||
#if defined(IS_AARCH64)
|
||||
#if defined(__ARM_BIG_ENDIAN)
|
||||
#define BLAKE3_USE_NEON 0
|
||||
#else
|
||||
#define BLAKE3_USE_NEON 1
|
||||
#endif
|
||||
#else
|
||||
#define BLAKE3_USE_NEON 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(IS_X86)
|
||||
#define MAX_SIMD_DEGREE 16
|
||||
#elif BLAKE3_USE_NEON == 1
|
||||
#define MAX_SIMD_DEGREE 4
|
||||
#else
|
||||
#define MAX_SIMD_DEGREE 1
|
||||
#endif
|
||||
|
||||
// There are some places where we want a static size that's equal to the
|
||||
// MAX_SIMD_DEGREE, but also at least 2.
|
||||
#define MAX_SIMD_DEGREE_OR_2 (MAX_SIMD_DEGREE > 2 ? MAX_SIMD_DEGREE : 2)
|
||||
|
||||
static const uint32_t IV[8] = {0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL,
|
||||
0xA54FF53AUL, 0x510E527FUL, 0x9B05688CUL,
|
||||
0x1F83D9ABUL, 0x5BE0CD19UL};
|
||||
|
||||
static const uint8_t MSG_SCHEDULE[7][16] = {
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8},
|
||||
{3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1},
|
||||
{10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6},
|
||||
{12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4},
|
||||
{9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7},
|
||||
{11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13},
|
||||
};
|
||||
|
||||
/* Find index of the highest set bit */
|
||||
/* x is assumed to be nonzero. */
|
||||
static unsigned int highest_one(uint64_t x) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return 63 ^ (unsigned int)__builtin_clzll(x);
|
||||
#elif defined(_MSC_VER) && defined(IS_X86_64)
|
||||
unsigned long index;
|
||||
_BitScanReverse64(&index, x);
|
||||
return index;
|
||||
#elif defined(_MSC_VER) && defined(IS_X86_32)
|
||||
if(x >> 32) {
|
||||
unsigned long index;
|
||||
_BitScanReverse(&index, (unsigned long)(x >> 32));
|
||||
return 32 + index;
|
||||
} else {
|
||||
unsigned long index;
|
||||
_BitScanReverse(&index, (unsigned long)x);
|
||||
return index;
|
||||
}
|
||||
#else
|
||||
unsigned int c = 0;
|
||||
if(x & 0xffffffff00000000ULL) { x >>= 32; c += 32; }
|
||||
if(x & 0x00000000ffff0000ULL) { x >>= 16; c += 16; }
|
||||
if(x & 0x000000000000ff00ULL) { x >>= 8; c += 8; }
|
||||
if(x & 0x00000000000000f0ULL) { x >>= 4; c += 4; }
|
||||
if(x & 0x000000000000000cULL) { x >>= 2; c += 2; }
|
||||
if(x & 0x0000000000000002ULL) { c += 1; }
|
||||
return c;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of 1 bits.
|
||||
INLINE unsigned int popcnt(uint64_t x) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return (unsigned int)__builtin_popcountll(x);
|
||||
#else
|
||||
unsigned int count = 0;
|
||||
while (x != 0) {
|
||||
count += 1;
|
||||
x &= x - 1;
|
||||
}
|
||||
return count;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Largest power of two less than or equal to x. As a special case, returns 1
|
||||
// when x is 0.
|
||||
INLINE uint64_t round_down_to_power_of_2(uint64_t x) {
|
||||
return 1ULL << highest_one(x | 1);
|
||||
}
|
||||
|
||||
INLINE uint32_t counter_low(uint64_t counter) { return (uint32_t)counter; }
|
||||
|
||||
INLINE uint32_t counter_high(uint64_t counter) {
|
||||
return (uint32_t)(counter >> 32);
|
||||
}
|
||||
|
||||
INLINE uint32_t load32(const void *src) {
|
||||
const uint8_t *p = (const uint8_t *)src;
|
||||
return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) |
|
||||
((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24);
|
||||
}
|
||||
|
||||
INLINE void load_key_words(const uint8_t key[BLAKE3_KEY_LEN],
|
||||
uint32_t key_words[8]) {
|
||||
key_words[0] = load32(&key[0 * 4]);
|
||||
key_words[1] = load32(&key[1 * 4]);
|
||||
key_words[2] = load32(&key[2 * 4]);
|
||||
key_words[3] = load32(&key[3 * 4]);
|
||||
key_words[4] = load32(&key[4 * 4]);
|
||||
key_words[5] = load32(&key[5 * 4]);
|
||||
key_words[6] = load32(&key[6 * 4]);
|
||||
key_words[7] = load32(&key[7 * 4]);
|
||||
}
|
||||
|
||||
INLINE void store32(void *dst, uint32_t w) {
|
||||
uint8_t *p = (uint8_t *)dst;
|
||||
p[0] = (uint8_t)(w >> 0);
|
||||
p[1] = (uint8_t)(w >> 8);
|
||||
p[2] = (uint8_t)(w >> 16);
|
||||
p[3] = (uint8_t)(w >> 24);
|
||||
}
|
||||
|
||||
INLINE void store_cv_words(uint8_t bytes_out[32], uint32_t cv_words[8]) {
|
||||
store32(&bytes_out[0 * 4], cv_words[0]);
|
||||
store32(&bytes_out[1 * 4], cv_words[1]);
|
||||
store32(&bytes_out[2 * 4], cv_words[2]);
|
||||
store32(&bytes_out[3 * 4], cv_words[3]);
|
||||
store32(&bytes_out[4 * 4], cv_words[4]);
|
||||
store32(&bytes_out[5 * 4], cv_words[5]);
|
||||
store32(&bytes_out[6 * 4], cv_words[6]);
|
||||
store32(&bytes_out[7 * 4], cv_words[7]);
|
||||
}
|
||||
|
||||
void blake3_compress_in_place(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t out[64]);
|
||||
|
||||
void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out);
|
||||
|
||||
size_t blake3_simd_degree(void);
|
||||
|
||||
|
||||
// Declarations for implementation-specific functions.
|
||||
void blake3_compress_in_place_portable(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof_portable(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
|
||||
void blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
void blake3_compress_in_place_sse2(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
void blake3_compress_xof_sse2(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
void blake3_hash_many_sse2(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
void blake3_compress_in_place_sse41(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
void blake3_compress_xof_sse41(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
void blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
void blake3_hash_many_avx2(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
void blake3_compress_in_place_avx512(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof_avx512(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
|
||||
void blake3_hash_many_avx512(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* BLAKE3_IMPL_H */
|
||||
@@ -1,368 +0,0 @@
|
||||
#include "blake3_impl.h"
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
#ifdef __ARM_BIG_ENDIAN
|
||||
#error "This implementation only supports little-endian ARM."
|
||||
// It might be that all we need for big-endian support here is to get the loads
|
||||
// and stores right, but step zero would be finding a way to test it in CI.
|
||||
#endif
|
||||
|
||||
INLINE uint32x4_t loadu_128(const uint8_t src[16]) {
|
||||
// vld1q_u32 has alignment requirements. Don't use it.
|
||||
uint32x4_t x;
|
||||
memcpy(&x, src, 16);
|
||||
return x;
|
||||
}
|
||||
|
||||
INLINE void storeu_128(uint32x4_t src, uint8_t dest[16]) {
|
||||
// vst1q_u32 has alignment requirements. Don't use it.
|
||||
memcpy(dest, &src, 16);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t add_128(uint32x4_t a, uint32x4_t b) {
|
||||
return vaddq_u32(a, b);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t xor_128(uint32x4_t a, uint32x4_t b) {
|
||||
return veorq_u32(a, b);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t set1_128(uint32_t x) { return vld1q_dup_u32(&x); }
|
||||
|
||||
INLINE uint32x4_t set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
uint32_t array[4] = {a, b, c, d};
|
||||
return vld1q_u32(array);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t rot16_128(uint32x4_t x) {
|
||||
// The straightfoward implementation would be two shifts and an or, but that's
|
||||
// slower on microarchitectures we've tested. See
|
||||
// https://github.com/BLAKE3-team/BLAKE3/pull/319.
|
||||
// return vorrq_u32(vshrq_n_u32(x, 16), vshlq_n_u32(x, 32 - 16));
|
||||
return vreinterpretq_u32_u16(vrev32q_u16(vreinterpretq_u16_u32(x)));
|
||||
}
|
||||
|
||||
INLINE uint32x4_t rot12_128(uint32x4_t x) {
|
||||
// See comment in rot16_128.
|
||||
// return vorrq_u32(vshrq_n_u32(x, 12), vshlq_n_u32(x, 32 - 12));
|
||||
return vsriq_n_u32(vshlq_n_u32(x, 32-12), x, 12);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t rot8_128(uint32x4_t x) {
|
||||
// See comment in rot16_128.
|
||||
// return vorrq_u32(vshrq_n_u32(x, 8), vshlq_n_u32(x, 32 - 8));
|
||||
#if defined(__clang__)
|
||||
return vreinterpretq_u32_u8(__builtin_shufflevector(vreinterpretq_u8_u32(x), vreinterpretq_u8_u32(x), 1,2,3,0,5,6,7,4,9,10,11,8,13,14,15,12));
|
||||
#elif __GNUC__ * 10000 + __GNUC_MINOR__ * 100 >=40700
|
||||
static const uint8x16_t r8 = {1,2,3,0,5,6,7,4,9,10,11,8,13,14,15,12};
|
||||
return vreinterpretq_u32_u8(__builtin_shuffle(vreinterpretq_u8_u32(x), vreinterpretq_u8_u32(x), r8));
|
||||
#else
|
||||
return vsriq_n_u32(vshlq_n_u32(x, 32-8), x, 8);
|
||||
#endif
|
||||
}
|
||||
|
||||
INLINE uint32x4_t rot7_128(uint32x4_t x) {
|
||||
// See comment in rot16_128.
|
||||
// return vorrq_u32(vshrq_n_u32(x, 7), vshlq_n_u32(x, 32 - 7));
|
||||
return vsriq_n_u32(vshlq_n_u32(x, 32-7), x, 7);
|
||||
}
|
||||
|
||||
// TODO: compress_neon
|
||||
|
||||
// TODO: hash2_neon
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* hash4_neon
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
INLINE void round_fn4(uint32x4_t v[16], uint32x4_t m[16], size_t r) {
|
||||
v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);
|
||||
v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);
|
||||
v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);
|
||||
v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);
|
||||
v[0] = add_128(v[0], v[4]);
|
||||
v[1] = add_128(v[1], v[5]);
|
||||
v[2] = add_128(v[2], v[6]);
|
||||
v[3] = add_128(v[3], v[7]);
|
||||
v[12] = xor_128(v[12], v[0]);
|
||||
v[13] = xor_128(v[13], v[1]);
|
||||
v[14] = xor_128(v[14], v[2]);
|
||||
v[15] = xor_128(v[15], v[3]);
|
||||
v[12] = rot16_128(v[12]);
|
||||
v[13] = rot16_128(v[13]);
|
||||
v[14] = rot16_128(v[14]);
|
||||
v[15] = rot16_128(v[15]);
|
||||
v[8] = add_128(v[8], v[12]);
|
||||
v[9] = add_128(v[9], v[13]);
|
||||
v[10] = add_128(v[10], v[14]);
|
||||
v[11] = add_128(v[11], v[15]);
|
||||
v[4] = xor_128(v[4], v[8]);
|
||||
v[5] = xor_128(v[5], v[9]);
|
||||
v[6] = xor_128(v[6], v[10]);
|
||||
v[7] = xor_128(v[7], v[11]);
|
||||
v[4] = rot12_128(v[4]);
|
||||
v[5] = rot12_128(v[5]);
|
||||
v[6] = rot12_128(v[6]);
|
||||
v[7] = rot12_128(v[7]);
|
||||
v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);
|
||||
v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);
|
||||
v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);
|
||||
v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);
|
||||
v[0] = add_128(v[0], v[4]);
|
||||
v[1] = add_128(v[1], v[5]);
|
||||
v[2] = add_128(v[2], v[6]);
|
||||
v[3] = add_128(v[3], v[7]);
|
||||
v[12] = xor_128(v[12], v[0]);
|
||||
v[13] = xor_128(v[13], v[1]);
|
||||
v[14] = xor_128(v[14], v[2]);
|
||||
v[15] = xor_128(v[15], v[3]);
|
||||
v[12] = rot8_128(v[12]);
|
||||
v[13] = rot8_128(v[13]);
|
||||
v[14] = rot8_128(v[14]);
|
||||
v[15] = rot8_128(v[15]);
|
||||
v[8] = add_128(v[8], v[12]);
|
||||
v[9] = add_128(v[9], v[13]);
|
||||
v[10] = add_128(v[10], v[14]);
|
||||
v[11] = add_128(v[11], v[15]);
|
||||
v[4] = xor_128(v[4], v[8]);
|
||||
v[5] = xor_128(v[5], v[9]);
|
||||
v[6] = xor_128(v[6], v[10]);
|
||||
v[7] = xor_128(v[7], v[11]);
|
||||
v[4] = rot7_128(v[4]);
|
||||
v[5] = rot7_128(v[5]);
|
||||
v[6] = rot7_128(v[6]);
|
||||
v[7] = rot7_128(v[7]);
|
||||
|
||||
v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);
|
||||
v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);
|
||||
v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);
|
||||
v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);
|
||||
v[0] = add_128(v[0], v[5]);
|
||||
v[1] = add_128(v[1], v[6]);
|
||||
v[2] = add_128(v[2], v[7]);
|
||||
v[3] = add_128(v[3], v[4]);
|
||||
v[15] = xor_128(v[15], v[0]);
|
||||
v[12] = xor_128(v[12], v[1]);
|
||||
v[13] = xor_128(v[13], v[2]);
|
||||
v[14] = xor_128(v[14], v[3]);
|
||||
v[15] = rot16_128(v[15]);
|
||||
v[12] = rot16_128(v[12]);
|
||||
v[13] = rot16_128(v[13]);
|
||||
v[14] = rot16_128(v[14]);
|
||||
v[10] = add_128(v[10], v[15]);
|
||||
v[11] = add_128(v[11], v[12]);
|
||||
v[8] = add_128(v[8], v[13]);
|
||||
v[9] = add_128(v[9], v[14]);
|
||||
v[5] = xor_128(v[5], v[10]);
|
||||
v[6] = xor_128(v[6], v[11]);
|
||||
v[7] = xor_128(v[7], v[8]);
|
||||
v[4] = xor_128(v[4], v[9]);
|
||||
v[5] = rot12_128(v[5]);
|
||||
v[6] = rot12_128(v[6]);
|
||||
v[7] = rot12_128(v[7]);
|
||||
v[4] = rot12_128(v[4]);
|
||||
v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);
|
||||
v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);
|
||||
v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);
|
||||
v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);
|
||||
v[0] = add_128(v[0], v[5]);
|
||||
v[1] = add_128(v[1], v[6]);
|
||||
v[2] = add_128(v[2], v[7]);
|
||||
v[3] = add_128(v[3], v[4]);
|
||||
v[15] = xor_128(v[15], v[0]);
|
||||
v[12] = xor_128(v[12], v[1]);
|
||||
v[13] = xor_128(v[13], v[2]);
|
||||
v[14] = xor_128(v[14], v[3]);
|
||||
v[15] = rot8_128(v[15]);
|
||||
v[12] = rot8_128(v[12]);
|
||||
v[13] = rot8_128(v[13]);
|
||||
v[14] = rot8_128(v[14]);
|
||||
v[10] = add_128(v[10], v[15]);
|
||||
v[11] = add_128(v[11], v[12]);
|
||||
v[8] = add_128(v[8], v[13]);
|
||||
v[9] = add_128(v[9], v[14]);
|
||||
v[5] = xor_128(v[5], v[10]);
|
||||
v[6] = xor_128(v[6], v[11]);
|
||||
v[7] = xor_128(v[7], v[8]);
|
||||
v[4] = xor_128(v[4], v[9]);
|
||||
v[5] = rot7_128(v[5]);
|
||||
v[6] = rot7_128(v[6]);
|
||||
v[7] = rot7_128(v[7]);
|
||||
v[4] = rot7_128(v[4]);
|
||||
}
|
||||
|
||||
INLINE void transpose_vecs_128(uint32x4_t vecs[4]) {
|
||||
// Individually transpose the four 2x2 sub-matrices in each corner.
|
||||
uint32x4x2_t rows01 = vtrnq_u32(vecs[0], vecs[1]);
|
||||
uint32x4x2_t rows23 = vtrnq_u32(vecs[2], vecs[3]);
|
||||
|
||||
// Swap the top-right and bottom-left 2x2s (which just got transposed).
|
||||
vecs[0] =
|
||||
vcombine_u32(vget_low_u32(rows01.val[0]), vget_low_u32(rows23.val[0]));
|
||||
vecs[1] =
|
||||
vcombine_u32(vget_low_u32(rows01.val[1]), vget_low_u32(rows23.val[1]));
|
||||
vecs[2] =
|
||||
vcombine_u32(vget_high_u32(rows01.val[0]), vget_high_u32(rows23.val[0]));
|
||||
vecs[3] =
|
||||
vcombine_u32(vget_high_u32(rows01.val[1]), vget_high_u32(rows23.val[1]));
|
||||
}
|
||||
|
||||
INLINE void transpose_msg_vecs4(const uint8_t *const *inputs,
|
||||
size_t block_offset, uint32x4_t out[16]) {
|
||||
out[0] = loadu_128(&inputs[0][block_offset + 0 * sizeof(uint32x4_t)]);
|
||||
out[1] = loadu_128(&inputs[1][block_offset + 0 * sizeof(uint32x4_t)]);
|
||||
out[2] = loadu_128(&inputs[2][block_offset + 0 * sizeof(uint32x4_t)]);
|
||||
out[3] = loadu_128(&inputs[3][block_offset + 0 * sizeof(uint32x4_t)]);
|
||||
out[4] = loadu_128(&inputs[0][block_offset + 1 * sizeof(uint32x4_t)]);
|
||||
out[5] = loadu_128(&inputs[1][block_offset + 1 * sizeof(uint32x4_t)]);
|
||||
out[6] = loadu_128(&inputs[2][block_offset + 1 * sizeof(uint32x4_t)]);
|
||||
out[7] = loadu_128(&inputs[3][block_offset + 1 * sizeof(uint32x4_t)]);
|
||||
out[8] = loadu_128(&inputs[0][block_offset + 2 * sizeof(uint32x4_t)]);
|
||||
out[9] = loadu_128(&inputs[1][block_offset + 2 * sizeof(uint32x4_t)]);
|
||||
out[10] = loadu_128(&inputs[2][block_offset + 2 * sizeof(uint32x4_t)]);
|
||||
out[11] = loadu_128(&inputs[3][block_offset + 2 * sizeof(uint32x4_t)]);
|
||||
out[12] = loadu_128(&inputs[0][block_offset + 3 * sizeof(uint32x4_t)]);
|
||||
out[13] = loadu_128(&inputs[1][block_offset + 3 * sizeof(uint32x4_t)]);
|
||||
out[14] = loadu_128(&inputs[2][block_offset + 3 * sizeof(uint32x4_t)]);
|
||||
out[15] = loadu_128(&inputs[3][block_offset + 3 * sizeof(uint32x4_t)]);
|
||||
transpose_vecs_128(&out[0]);
|
||||
transpose_vecs_128(&out[4]);
|
||||
transpose_vecs_128(&out[8]);
|
||||
transpose_vecs_128(&out[12]);
|
||||
}
|
||||
|
||||
INLINE void load_counters4(uint64_t counter, bool increment_counter,
|
||||
uint32x4_t *out_low, uint32x4_t *out_high) {
|
||||
uint64_t mask = (increment_counter ? ~0 : 0);
|
||||
*out_low = set4(
|
||||
counter_low(counter + (mask & 0)), counter_low(counter + (mask & 1)),
|
||||
counter_low(counter + (mask & 2)), counter_low(counter + (mask & 3)));
|
||||
*out_high = set4(
|
||||
counter_high(counter + (mask & 0)), counter_high(counter + (mask & 1)),
|
||||
counter_high(counter + (mask & 2)), counter_high(counter + (mask & 3)));
|
||||
}
|
||||
|
||||
void blake3_hash4_neon(const uint8_t *const *inputs, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out) {
|
||||
uint32x4_t h_vecs[8] = {
|
||||
set1_128(key[0]), set1_128(key[1]), set1_128(key[2]), set1_128(key[3]),
|
||||
set1_128(key[4]), set1_128(key[5]), set1_128(key[6]), set1_128(key[7]),
|
||||
};
|
||||
uint32x4_t counter_low_vec, counter_high_vec;
|
||||
load_counters4(counter, increment_counter, &counter_low_vec,
|
||||
&counter_high_vec);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
|
||||
for (size_t block = 0; block < blocks; block++) {
|
||||
if (block + 1 == blocks) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
uint32x4_t block_len_vec = set1_128(BLAKE3_BLOCK_LEN);
|
||||
uint32x4_t block_flags_vec = set1_128(block_flags);
|
||||
uint32x4_t msg_vecs[16];
|
||||
transpose_msg_vecs4(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);
|
||||
|
||||
uint32x4_t v[16] = {
|
||||
h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3],
|
||||
h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7],
|
||||
set1_128(IV[0]), set1_128(IV[1]), set1_128(IV[2]), set1_128(IV[3]),
|
||||
counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec,
|
||||
};
|
||||
round_fn4(v, msg_vecs, 0);
|
||||
round_fn4(v, msg_vecs, 1);
|
||||
round_fn4(v, msg_vecs, 2);
|
||||
round_fn4(v, msg_vecs, 3);
|
||||
round_fn4(v, msg_vecs, 4);
|
||||
round_fn4(v, msg_vecs, 5);
|
||||
round_fn4(v, msg_vecs, 6);
|
||||
h_vecs[0] = xor_128(v[0], v[8]);
|
||||
h_vecs[1] = xor_128(v[1], v[9]);
|
||||
h_vecs[2] = xor_128(v[2], v[10]);
|
||||
h_vecs[3] = xor_128(v[3], v[11]);
|
||||
h_vecs[4] = xor_128(v[4], v[12]);
|
||||
h_vecs[5] = xor_128(v[5], v[13]);
|
||||
h_vecs[6] = xor_128(v[6], v[14]);
|
||||
h_vecs[7] = xor_128(v[7], v[15]);
|
||||
|
||||
block_flags = flags;
|
||||
}
|
||||
|
||||
transpose_vecs_128(&h_vecs[0]);
|
||||
transpose_vecs_128(&h_vecs[4]);
|
||||
// The first four vecs now contain the first half of each output, and the
|
||||
// second four vecs contain the second half of each output.
|
||||
storeu_128(h_vecs[0], &out[0 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[4], &out[1 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[1], &out[2 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[5], &out[3 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[2], &out[4 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[6], &out[5 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[3], &out[6 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[7], &out[7 * sizeof(uint32x4_t)]);
|
||||
}
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* hash_many_neon
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
void blake3_compress_in_place_portable(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
INLINE void hash_one_neon(const uint8_t *input, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
uint8_t flags, uint8_t flags_start, uint8_t flags_end,
|
||||
uint8_t out[BLAKE3_OUT_LEN]) {
|
||||
uint32_t cv[8];
|
||||
memcpy(cv, key, BLAKE3_KEY_LEN);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
while (blocks > 0) {
|
||||
if (blocks == 1) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
// TODO: Implement compress_neon. However note that according to
|
||||
// https://github.com/BLAKE2/BLAKE2/commit/7965d3e6e1b4193438b8d3a656787587d2579227,
|
||||
// compress_neon might not be any faster than compress_portable.
|
||||
blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter,
|
||||
block_flags);
|
||||
input = &input[BLAKE3_BLOCK_LEN];
|
||||
blocks -= 1;
|
||||
block_flags = flags;
|
||||
}
|
||||
memcpy(out, cv, BLAKE3_OUT_LEN);
|
||||
}
|
||||
|
||||
void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out) {
|
||||
while (num_inputs >= 4) {
|
||||
blake3_hash4_neon(inputs, blocks, key, counter, increment_counter, flags,
|
||||
flags_start, flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 4;
|
||||
}
|
||||
inputs += 4;
|
||||
num_inputs -= 4;
|
||||
out = &out[4 * BLAKE3_OUT_LEN];
|
||||
}
|
||||
while (num_inputs > 0) {
|
||||
hash_one_neon(inputs[0], blocks, key, counter, flags, flags_start,
|
||||
flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 1;
|
||||
}
|
||||
inputs += 1;
|
||||
num_inputs -= 1;
|
||||
out = &out[BLAKE3_OUT_LEN];
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
#include "blake3_impl.h"
|
||||
#include <string.h>
|
||||
|
||||
INLINE uint32_t rotr32(uint32_t w, uint32_t c) {
|
||||
return (w >> c) | (w << (32 - c));
|
||||
}
|
||||
|
||||
INLINE void g(uint32_t *state, size_t a, size_t b, size_t c, size_t d,
|
||||
uint32_t x, uint32_t y) {
|
||||
state[a] = state[a] + state[b] + x;
|
||||
state[d] = rotr32(state[d] ^ state[a], 16);
|
||||
state[c] = state[c] + state[d];
|
||||
state[b] = rotr32(state[b] ^ state[c], 12);
|
||||
state[a] = state[a] + state[b] + y;
|
||||
state[d] = rotr32(state[d] ^ state[a], 8);
|
||||
state[c] = state[c] + state[d];
|
||||
state[b] = rotr32(state[b] ^ state[c], 7);
|
||||
}
|
||||
|
||||
INLINE void round_fn(uint32_t state[16], const uint32_t *msg, size_t round) {
|
||||
// Select the message schedule based on the round.
|
||||
const uint8_t *schedule = MSG_SCHEDULE[round];
|
||||
|
||||
// Mix the columns.
|
||||
g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]);
|
||||
g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]);
|
||||
g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]);
|
||||
g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]);
|
||||
|
||||
// Mix the rows.
|
||||
g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]);
|
||||
g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]);
|
||||
g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]);
|
||||
g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);
|
||||
}
|
||||
|
||||
INLINE void compress_pre(uint32_t state[16], const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags) {
|
||||
uint32_t block_words[16];
|
||||
block_words[0] = load32(block + 4 * 0);
|
||||
block_words[1] = load32(block + 4 * 1);
|
||||
block_words[2] = load32(block + 4 * 2);
|
||||
block_words[3] = load32(block + 4 * 3);
|
||||
block_words[4] = load32(block + 4 * 4);
|
||||
block_words[5] = load32(block + 4 * 5);
|
||||
block_words[6] = load32(block + 4 * 6);
|
||||
block_words[7] = load32(block + 4 * 7);
|
||||
block_words[8] = load32(block + 4 * 8);
|
||||
block_words[9] = load32(block + 4 * 9);
|
||||
block_words[10] = load32(block + 4 * 10);
|
||||
block_words[11] = load32(block + 4 * 11);
|
||||
block_words[12] = load32(block + 4 * 12);
|
||||
block_words[13] = load32(block + 4 * 13);
|
||||
block_words[14] = load32(block + 4 * 14);
|
||||
block_words[15] = load32(block + 4 * 15);
|
||||
|
||||
state[0] = cv[0];
|
||||
state[1] = cv[1];
|
||||
state[2] = cv[2];
|
||||
state[3] = cv[3];
|
||||
state[4] = cv[4];
|
||||
state[5] = cv[5];
|
||||
state[6] = cv[6];
|
||||
state[7] = cv[7];
|
||||
state[8] = IV[0];
|
||||
state[9] = IV[1];
|
||||
state[10] = IV[2];
|
||||
state[11] = IV[3];
|
||||
state[12] = counter_low(counter);
|
||||
state[13] = counter_high(counter);
|
||||
state[14] = (uint32_t)block_len;
|
||||
state[15] = (uint32_t)flags;
|
||||
|
||||
round_fn(state, &block_words[0], 0);
|
||||
round_fn(state, &block_words[0], 1);
|
||||
round_fn(state, &block_words[0], 2);
|
||||
round_fn(state, &block_words[0], 3);
|
||||
round_fn(state, &block_words[0], 4);
|
||||
round_fn(state, &block_words[0], 5);
|
||||
round_fn(state, &block_words[0], 6);
|
||||
}
|
||||
|
||||
void blake3_compress_in_place_portable(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
uint32_t state[16];
|
||||
compress_pre(state, cv, block, block_len, counter, flags);
|
||||
cv[0] = state[0] ^ state[8];
|
||||
cv[1] = state[1] ^ state[9];
|
||||
cv[2] = state[2] ^ state[10];
|
||||
cv[3] = state[3] ^ state[11];
|
||||
cv[4] = state[4] ^ state[12];
|
||||
cv[5] = state[5] ^ state[13];
|
||||
cv[6] = state[6] ^ state[14];
|
||||
cv[7] = state[7] ^ state[15];
|
||||
}
|
||||
|
||||
void blake3_compress_xof_portable(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]) {
|
||||
uint32_t state[16];
|
||||
compress_pre(state, cv, block, block_len, counter, flags);
|
||||
|
||||
store32(&out[0 * 4], state[0] ^ state[8]);
|
||||
store32(&out[1 * 4], state[1] ^ state[9]);
|
||||
store32(&out[2 * 4], state[2] ^ state[10]);
|
||||
store32(&out[3 * 4], state[3] ^ state[11]);
|
||||
store32(&out[4 * 4], state[4] ^ state[12]);
|
||||
store32(&out[5 * 4], state[5] ^ state[13]);
|
||||
store32(&out[6 * 4], state[6] ^ state[14]);
|
||||
store32(&out[7 * 4], state[7] ^ state[15]);
|
||||
store32(&out[8 * 4], state[8] ^ cv[0]);
|
||||
store32(&out[9 * 4], state[9] ^ cv[1]);
|
||||
store32(&out[10 * 4], state[10] ^ cv[2]);
|
||||
store32(&out[11 * 4], state[11] ^ cv[3]);
|
||||
store32(&out[12 * 4], state[12] ^ cv[4]);
|
||||
store32(&out[13 * 4], state[13] ^ cv[5]);
|
||||
store32(&out[14 * 4], state[14] ^ cv[6]);
|
||||
store32(&out[15 * 4], state[15] ^ cv[7]);
|
||||
}
|
||||
|
||||
INLINE void hash_one_portable(const uint8_t *input, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {
|
||||
uint32_t cv[8];
|
||||
memcpy(cv, key, BLAKE3_KEY_LEN);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
while (blocks > 0) {
|
||||
if (blocks == 1) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter,
|
||||
block_flags);
|
||||
input = &input[BLAKE3_BLOCK_LEN];
|
||||
blocks -= 1;
|
||||
block_flags = flags;
|
||||
}
|
||||
store_cv_words(out, cv);
|
||||
}
|
||||
|
||||
void blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out) {
|
||||
while (num_inputs > 0) {
|
||||
hash_one_portable(inputs[0], blocks, key, counter, flags, flags_start,
|
||||
flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 1;
|
||||
}
|
||||
inputs += 1;
|
||||
num_inputs -= 1;
|
||||
out = &out[BLAKE3_OUT_LEN];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,330 +0,0 @@
|
||||
This work is released into the public domain with CC0 1.0. Alternatively, it is
|
||||
licensed under the Apache License 2.0.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 Jack O'Connor and Samuel Neves
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,616 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "blake3.h"
|
||||
#include "blake3_impl.h"
|
||||
|
||||
const char *blake3_version(void) { return BLAKE3_VERSION_STRING; }
|
||||
|
||||
INLINE void chunk_state_init(blake3_chunk_state *self, const uint32_t key[8],
|
||||
uint8_t flags) {
|
||||
memcpy(self->cv, key, BLAKE3_KEY_LEN);
|
||||
self->chunk_counter = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
self->buf_len = 0;
|
||||
self->blocks_compressed = 0;
|
||||
self->flags = flags;
|
||||
}
|
||||
|
||||
INLINE void chunk_state_reset(blake3_chunk_state *self, const uint32_t key[8],
|
||||
uint64_t chunk_counter) {
|
||||
memcpy(self->cv, key, BLAKE3_KEY_LEN);
|
||||
self->chunk_counter = chunk_counter;
|
||||
self->blocks_compressed = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
self->buf_len = 0;
|
||||
}
|
||||
|
||||
INLINE size_t chunk_state_len(const blake3_chunk_state *self) {
|
||||
return (BLAKE3_BLOCK_LEN * (size_t)self->blocks_compressed) +
|
||||
((size_t)self->buf_len);
|
||||
}
|
||||
|
||||
INLINE size_t chunk_state_fill_buf(blake3_chunk_state *self,
|
||||
const uint8_t *input, size_t input_len) {
|
||||
size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len);
|
||||
if (take > input_len) {
|
||||
take = input_len;
|
||||
}
|
||||
uint8_t *dest = self->buf + ((size_t)self->buf_len);
|
||||
memcpy(dest, input, take);
|
||||
self->buf_len += (uint8_t)take;
|
||||
return take;
|
||||
}
|
||||
|
||||
INLINE uint8_t chunk_state_maybe_start_flag(const blake3_chunk_state *self) {
|
||||
if (self->blocks_compressed == 0) {
|
||||
return CHUNK_START;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t input_cv[8];
|
||||
uint64_t counter;
|
||||
uint8_t block[BLAKE3_BLOCK_LEN];
|
||||
uint8_t block_len;
|
||||
uint8_t flags;
|
||||
} output_t;
|
||||
|
||||
INLINE output_t make_output(const uint32_t input_cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
output_t ret;
|
||||
memcpy(ret.input_cv, input_cv, 32);
|
||||
memcpy(ret.block, block, BLAKE3_BLOCK_LEN);
|
||||
ret.block_len = block_len;
|
||||
ret.counter = counter;
|
||||
ret.flags = flags;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Chaining values within a given chunk (specifically the compress_in_place
|
||||
// interface) are represented as words. This avoids unnecessary bytes<->words
|
||||
// conversion overhead in the portable implementation. However, the hash_many
|
||||
// interface handles both user input and parent node blocks, so it accepts
|
||||
// bytes. For that reason, chaining values in the CV stack are represented as
|
||||
// bytes.
|
||||
INLINE void output_chaining_value(const output_t *self, uint8_t cv[32]) {
|
||||
uint32_t cv_words[8];
|
||||
memcpy(cv_words, self->input_cv, 32);
|
||||
blake3_compress_in_place(cv_words, self->block, self->block_len,
|
||||
self->counter, self->flags);
|
||||
store_cv_words(cv, cv_words);
|
||||
}
|
||||
|
||||
INLINE void output_root_bytes(const output_t *self, uint64_t seek, uint8_t *out,
|
||||
size_t out_len) {
|
||||
uint64_t output_block_counter = seek / 64;
|
||||
size_t offset_within_block = seek % 64;
|
||||
uint8_t wide_buf[64];
|
||||
while (out_len > 0) {
|
||||
blake3_compress_xof(self->input_cv, self->block, self->block_len,
|
||||
output_block_counter, self->flags | ROOT, wide_buf);
|
||||
size_t available_bytes = 64 - offset_within_block;
|
||||
size_t memcpy_len;
|
||||
if (out_len > available_bytes) {
|
||||
memcpy_len = available_bytes;
|
||||
} else {
|
||||
memcpy_len = out_len;
|
||||
}
|
||||
memcpy(out, wide_buf + offset_within_block, memcpy_len);
|
||||
out += memcpy_len;
|
||||
out_len -= memcpy_len;
|
||||
output_block_counter += 1;
|
||||
offset_within_block = 0;
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void chunk_state_update(blake3_chunk_state *self, const uint8_t *input,
|
||||
size_t input_len) {
|
||||
if (self->buf_len > 0) {
|
||||
size_t take = chunk_state_fill_buf(self, input, input_len);
|
||||
input += take;
|
||||
input_len -= take;
|
||||
if (input_len > 0) {
|
||||
blake3_compress_in_place(
|
||||
self->cv, self->buf, BLAKE3_BLOCK_LEN, self->chunk_counter,
|
||||
self->flags | chunk_state_maybe_start_flag(self));
|
||||
self->blocks_compressed += 1;
|
||||
self->buf_len = 0;
|
||||
memset(self->buf, 0, BLAKE3_BLOCK_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
while (input_len > BLAKE3_BLOCK_LEN) {
|
||||
blake3_compress_in_place(self->cv, input, BLAKE3_BLOCK_LEN,
|
||||
self->chunk_counter,
|
||||
self->flags | chunk_state_maybe_start_flag(self));
|
||||
self->blocks_compressed += 1;
|
||||
input += BLAKE3_BLOCK_LEN;
|
||||
input_len -= BLAKE3_BLOCK_LEN;
|
||||
}
|
||||
|
||||
size_t take = chunk_state_fill_buf(self, input, input_len);
|
||||
input += take;
|
||||
input_len -= take;
|
||||
}
|
||||
|
||||
INLINE output_t chunk_state_output(const blake3_chunk_state *self) {
|
||||
uint8_t block_flags =
|
||||
self->flags | chunk_state_maybe_start_flag(self) | CHUNK_END;
|
||||
return make_output(self->cv, self->buf, self->buf_len, self->chunk_counter,
|
||||
block_flags);
|
||||
}
|
||||
|
||||
INLINE output_t parent_output(const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
const uint32_t key[8], uint8_t flags) {
|
||||
return make_output(key, block, BLAKE3_BLOCK_LEN, 0, flags | PARENT);
|
||||
}
|
||||
|
||||
// Given some input larger than one chunk, return the number of bytes that
|
||||
// should go in the left subtree. This is the largest power-of-2 number of
|
||||
// chunks that leaves at least 1 byte for the right subtree.
|
||||
INLINE size_t left_len(size_t content_len) {
|
||||
// Subtract 1 to reserve at least one byte for the right side. content_len
|
||||
// should always be greater than BLAKE3_CHUNK_LEN.
|
||||
size_t full_chunks = (content_len - 1) / BLAKE3_CHUNK_LEN;
|
||||
return round_down_to_power_of_2(full_chunks) * BLAKE3_CHUNK_LEN;
|
||||
}
|
||||
|
||||
// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE chunks at the same time
|
||||
// on a single thread. Write out the chunk chaining values and return the
|
||||
// number of chunks hashed. These chunks are never the root and never empty;
|
||||
// those cases use a different codepath.
|
||||
INLINE size_t compress_chunks_parallel(const uint8_t *input, size_t input_len,
|
||||
const uint32_t key[8],
|
||||
uint64_t chunk_counter, uint8_t flags,
|
||||
uint8_t *out) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(0 < input_len);
|
||||
assert(input_len <= MAX_SIMD_DEGREE * BLAKE3_CHUNK_LEN);
|
||||
#endif
|
||||
|
||||
const uint8_t *chunks_array[MAX_SIMD_DEGREE];
|
||||
size_t input_position = 0;
|
||||
size_t chunks_array_len = 0;
|
||||
while (input_len - input_position >= BLAKE3_CHUNK_LEN) {
|
||||
chunks_array[chunks_array_len] = &input[input_position];
|
||||
input_position += BLAKE3_CHUNK_LEN;
|
||||
chunks_array_len += 1;
|
||||
}
|
||||
|
||||
blake3_hash_many(chunks_array, chunks_array_len,
|
||||
BLAKE3_CHUNK_LEN / BLAKE3_BLOCK_LEN, key, chunk_counter,
|
||||
true, flags, CHUNK_START, CHUNK_END, out);
|
||||
|
||||
// Hash the remaining partial chunk, if there is one. Note that the empty
|
||||
// chunk (meaning the empty message) is a different codepath.
|
||||
if (input_len > input_position) {
|
||||
uint64_t counter = chunk_counter + (uint64_t)chunks_array_len;
|
||||
blake3_chunk_state chunk_state;
|
||||
chunk_state_init(&chunk_state, key, flags);
|
||||
chunk_state.chunk_counter = counter;
|
||||
chunk_state_update(&chunk_state, &input[input_position],
|
||||
input_len - input_position);
|
||||
output_t output = chunk_state_output(&chunk_state);
|
||||
output_chaining_value(&output, &out[chunks_array_len * BLAKE3_OUT_LEN]);
|
||||
return chunks_array_len + 1;
|
||||
} else {
|
||||
return chunks_array_len;
|
||||
}
|
||||
}
|
||||
|
||||
// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE parents at the same time
|
||||
// on a single thread. Write out the parent chaining values and return the
|
||||
// number of parents hashed. (If there's an odd input chaining value left over,
|
||||
// return it as an additional output.) These parents are never the root and
|
||||
// never empty; those cases use a different codepath.
|
||||
INLINE size_t compress_parents_parallel(const uint8_t *child_chaining_values,
|
||||
size_t num_chaining_values,
|
||||
const uint32_t key[8], uint8_t flags,
|
||||
uint8_t *out) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(2 <= num_chaining_values);
|
||||
assert(num_chaining_values <= 2 * MAX_SIMD_DEGREE_OR_2);
|
||||
#endif
|
||||
|
||||
const uint8_t *parents_array[MAX_SIMD_DEGREE_OR_2];
|
||||
size_t parents_array_len = 0;
|
||||
while (num_chaining_values - (2 * parents_array_len) >= 2) {
|
||||
parents_array[parents_array_len] =
|
||||
&child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN];
|
||||
parents_array_len += 1;
|
||||
}
|
||||
|
||||
blake3_hash_many(parents_array, parents_array_len, 1, key,
|
||||
0, // Parents always use counter 0.
|
||||
false, flags | PARENT,
|
||||
0, // Parents have no start flags.
|
||||
0, // Parents have no end flags.
|
||||
out);
|
||||
|
||||
// If there's an odd child left over, it becomes an output.
|
||||
if (num_chaining_values > 2 * parents_array_len) {
|
||||
memcpy(&out[parents_array_len * BLAKE3_OUT_LEN],
|
||||
&child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN],
|
||||
BLAKE3_OUT_LEN);
|
||||
return parents_array_len + 1;
|
||||
} else {
|
||||
return parents_array_len;
|
||||
}
|
||||
}
|
||||
|
||||
// The wide helper function returns (writes out) an array of chaining values
|
||||
// and returns the length of that array. The number of chaining values returned
|
||||
// is the dynamically detected SIMD degree, at most MAX_SIMD_DEGREE. Or fewer,
|
||||
// if the input is shorter than that many chunks. The reason for maintaining a
|
||||
// wide array of chaining values going back up the tree, is to allow the
|
||||
// implementation to hash as many parents in parallel as possible.
|
||||
//
|
||||
// As a special case when the SIMD degree is 1, this function will still return
|
||||
// at least 2 outputs. This guarantees that this function doesn't perform the
|
||||
// root compression. (If it did, it would use the wrong flags, and also we
|
||||
// wouldn't be able to implement extendable output.) Note that this function is
|
||||
// not used when the whole input is only 1 chunk long; that's a different
|
||||
// codepath.
|
||||
//
|
||||
// Why not just have the caller split the input on the first update(), instead
|
||||
// of implementing this special rule? Because we don't want to limit SIMD or
|
||||
// multi-threading parallelism for that update().
|
||||
static size_t blake3_compress_subtree_wide(const uint8_t *input,
|
||||
size_t input_len,
|
||||
const uint32_t key[8],
|
||||
uint64_t chunk_counter,
|
||||
uint8_t flags, uint8_t *out) {
|
||||
// Note that the single chunk case does *not* bump the SIMD degree up to 2
|
||||
// when it is 1. If this implementation adds multi-threading in the future,
|
||||
// this gives us the option of multi-threading even the 2-chunk case, which
|
||||
// can help performance on smaller platforms.
|
||||
if (input_len <= blake3_simd_degree() * BLAKE3_CHUNK_LEN) {
|
||||
return compress_chunks_parallel(input, input_len, key, chunk_counter, flags,
|
||||
out);
|
||||
}
|
||||
|
||||
// With more than simd_degree chunks, we need to recurse. Start by dividing
|
||||
// the input into left and right subtrees. (Note that this is only optimal
|
||||
// as long as the SIMD degree is a power of 2. If we ever get a SIMD degree
|
||||
// of 3 or something, we'll need a more complicated strategy.)
|
||||
size_t left_input_len = left_len(input_len);
|
||||
size_t right_input_len = input_len - left_input_len;
|
||||
const uint8_t *right_input = &input[left_input_len];
|
||||
uint64_t right_chunk_counter =
|
||||
chunk_counter + (uint64_t)(left_input_len / BLAKE3_CHUNK_LEN);
|
||||
|
||||
// Make space for the child outputs. Here we use MAX_SIMD_DEGREE_OR_2 to
|
||||
// account for the special case of returning 2 outputs when the SIMD degree
|
||||
// is 1.
|
||||
uint8_t cv_array[2 * MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];
|
||||
size_t degree = blake3_simd_degree();
|
||||
if (left_input_len > BLAKE3_CHUNK_LEN && degree == 1) {
|
||||
// The special case: We always use a degree of at least two, to make
|
||||
// sure there are two outputs. Except, as noted above, at the chunk
|
||||
// level, where we allow degree=1. (Note that the 1-chunk-input case is
|
||||
// a different codepath.)
|
||||
degree = 2;
|
||||
}
|
||||
uint8_t *right_cvs = &cv_array[degree * BLAKE3_OUT_LEN];
|
||||
|
||||
// Recurse! If this implementation adds multi-threading support in the
|
||||
// future, this is where it will go.
|
||||
size_t left_n = blake3_compress_subtree_wide(input, left_input_len, key,
|
||||
chunk_counter, flags, cv_array);
|
||||
size_t right_n = blake3_compress_subtree_wide(
|
||||
right_input, right_input_len, key, right_chunk_counter, flags, right_cvs);
|
||||
|
||||
// The special case again. If simd_degree=1, then we'll have left_n=1 and
|
||||
// right_n=1. Rather than compressing them into a single output, return
|
||||
// them directly, to make sure we always have at least two outputs.
|
||||
if (left_n == 1) {
|
||||
memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Otherwise, do one layer of parent node compression.
|
||||
size_t num_chaining_values = left_n + right_n;
|
||||
return compress_parents_parallel(cv_array, num_chaining_values, key, flags,
|
||||
out);
|
||||
}
|
||||
|
||||
// Hash a subtree with compress_subtree_wide(), and then condense the resulting
|
||||
// list of chaining values down to a single parent node. Don't compress that
|
||||
// last parent node, however. Instead, return its message bytes (the
|
||||
// concatenated chaining values of its children). This is necessary when the
|
||||
// first call to update() supplies a complete subtree, because the topmost
|
||||
// parent node of that subtree could end up being the root. It's also necessary
|
||||
// for extended output in the general case.
|
||||
//
|
||||
// As with compress_subtree_wide(), this function is not used on inputs of 1
|
||||
// chunk or less. That's a different codepath.
|
||||
INLINE void compress_subtree_to_parent_node(
|
||||
const uint8_t *input, size_t input_len, const uint32_t key[8],
|
||||
uint64_t chunk_counter, uint8_t flags, uint8_t out[2 * BLAKE3_OUT_LEN]) {
|
||||
#if defined(BLAKE3_TESTING)
|
||||
assert(input_len > BLAKE3_CHUNK_LEN);
|
||||
#endif
|
||||
|
||||
uint8_t cv_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];
|
||||
size_t num_cvs = blake3_compress_subtree_wide(input, input_len, key,
|
||||
chunk_counter, flags, cv_array);
|
||||
assert(num_cvs <= MAX_SIMD_DEGREE_OR_2);
|
||||
|
||||
// If MAX_SIMD_DEGREE is greater than 2 and there's enough input,
|
||||
// compress_subtree_wide() returns more than 2 chaining values. Condense
|
||||
// them into 2 by forming parent nodes repeatedly.
|
||||
uint8_t out_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN / 2];
|
||||
// The second half of this loop condition is always true, and we just
|
||||
// asserted it above. But GCC can't tell that it's always true, and if NDEBUG
|
||||
// is set on platforms where MAX_SIMD_DEGREE_OR_2 == 2, GCC emits spurious
|
||||
// warnings here. GCC 8.5 is particularly sensitive, so if you're changing
|
||||
// this code, test it against that version.
|
||||
while (num_cvs > 2 && num_cvs <= MAX_SIMD_DEGREE_OR_2) {
|
||||
num_cvs =
|
||||
compress_parents_parallel(cv_array, num_cvs, key, flags, out_array);
|
||||
memcpy(cv_array, out_array, num_cvs * BLAKE3_OUT_LEN);
|
||||
}
|
||||
memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN);
|
||||
}
|
||||
|
||||
INLINE void hasher_init_base(blake3_hasher *self, const uint32_t key[8],
|
||||
uint8_t flags) {
|
||||
memcpy(self->key, key, BLAKE3_KEY_LEN);
|
||||
chunk_state_init(&self->chunk, key, flags);
|
||||
self->cv_stack_len = 0;
|
||||
}
|
||||
|
||||
void blake3_hasher_init(blake3_hasher *self) { hasher_init_base(self, IV, 0); }
|
||||
|
||||
void blake3_hasher_init_keyed(blake3_hasher *self,
|
||||
const uint8_t key[BLAKE3_KEY_LEN]) {
|
||||
uint32_t key_words[8];
|
||||
load_key_words(key, key_words);
|
||||
hasher_init_base(self, key_words, KEYED_HASH);
|
||||
}
|
||||
|
||||
void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
|
||||
size_t context_len) {
|
||||
blake3_hasher context_hasher;
|
||||
hasher_init_base(&context_hasher, IV, DERIVE_KEY_CONTEXT);
|
||||
blake3_hasher_update(&context_hasher, context, context_len);
|
||||
uint8_t context_key[BLAKE3_KEY_LEN];
|
||||
blake3_hasher_finalize(&context_hasher, context_key, BLAKE3_KEY_LEN);
|
||||
uint32_t context_key_words[8];
|
||||
load_key_words(context_key, context_key_words);
|
||||
hasher_init_base(self, context_key_words, DERIVE_KEY_MATERIAL);
|
||||
}
|
||||
|
||||
void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context) {
|
||||
blake3_hasher_init_derive_key_raw(self, context, strlen(context));
|
||||
}
|
||||
|
||||
// As described in hasher_push_cv() below, we do "lazy merging", delaying
|
||||
// merges until right before the next CV is about to be added. This is
|
||||
// different from the reference implementation. Another difference is that we
|
||||
// aren't always merging 1 chunk at a time. Instead, each CV might represent
|
||||
// any power-of-two number of chunks, as long as the smaller-above-larger stack
|
||||
// order is maintained. Instead of the "count the trailing 0-bits" algorithm
|
||||
// described in the spec, we use a "count the total number of 1-bits" variant
|
||||
// that doesn't require us to retain the subtree size of the CV on top of the
|
||||
// stack. The principle is the same: each CV that should remain in the stack is
|
||||
// represented by a 1-bit in the total number of chunks (or bytes) so far.
|
||||
INLINE void hasher_merge_cv_stack(blake3_hasher *self, uint64_t total_len) {
|
||||
size_t post_merge_stack_len = (size_t)popcnt(total_len);
|
||||
while (self->cv_stack_len > post_merge_stack_len) {
|
||||
uint8_t *parent_node =
|
||||
&self->cv_stack[(self->cv_stack_len - 2) * BLAKE3_OUT_LEN];
|
||||
output_t output = parent_output(parent_node, self->key, self->chunk.flags);
|
||||
output_chaining_value(&output, parent_node);
|
||||
self->cv_stack_len -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// In reference_impl.rs, we merge the new CV with existing CVs from the stack
|
||||
// before pushing it. We can do that because we know more input is coming, so
|
||||
// we know none of the merges are root.
|
||||
//
|
||||
// This setting is different. We want to feed as much input as possible to
|
||||
// compress_subtree_wide(), without setting aside anything for the chunk_state.
|
||||
// If the user gives us 64 KiB, we want to parallelize over all 64 KiB at once
|
||||
// as a single subtree, if at all possible.
|
||||
//
|
||||
// This leads to two problems:
|
||||
// 1) This 64 KiB input might be the only call that ever gets made to update.
|
||||
// In this case, the root node of the 64 KiB subtree would be the root node
|
||||
// of the whole tree, and it would need to be ROOT finalized. We can't
|
||||
// compress it until we know.
|
||||
// 2) This 64 KiB input might complete a larger tree, whose root node is
|
||||
// similarly going to be the the root of the whole tree. For example, maybe
|
||||
// we have 196 KiB (that is, 128 + 64) hashed so far. We can't compress the
|
||||
// node at the root of the 256 KiB subtree until we know how to finalize it.
|
||||
//
|
||||
// The second problem is solved with "lazy merging". That is, when we're about
|
||||
// to add a CV to the stack, we don't merge it with anything first, as the
|
||||
// reference impl does. Instead we do merges using the *previous* CV that was
|
||||
// added, which is sitting on top of the stack, and we put the new CV
|
||||
// (unmerged) on top of the stack afterwards. This guarantees that we never
|
||||
// merge the root node until finalize().
|
||||
//
|
||||
// Solving the first problem requires an additional tool,
|
||||
// compress_subtree_to_parent_node(). That function always returns the top
|
||||
// *two* chaining values of the subtree it's compressing. We then do lazy
|
||||
// merging with each of them separately, so that the second CV will always
|
||||
// remain unmerged. (That also helps us support extendable output when we're
|
||||
// hashing an input all-at-once.)
|
||||
INLINE void hasher_push_cv(blake3_hasher *self, uint8_t new_cv[BLAKE3_OUT_LEN],
|
||||
uint64_t chunk_counter) {
|
||||
hasher_merge_cv_stack(self, chunk_counter);
|
||||
memcpy(&self->cv_stack[self->cv_stack_len * BLAKE3_OUT_LEN], new_cv,
|
||||
BLAKE3_OUT_LEN);
|
||||
self->cv_stack_len += 1;
|
||||
}
|
||||
|
||||
void blake3_hasher_update(blake3_hasher *self, const void *input,
|
||||
size_t input_len) {
|
||||
// Explicitly checking for zero avoids causing UB by passing a null pointer
|
||||
// to memcpy. This comes up in practice with things like:
|
||||
// std::vector<uint8_t> v;
|
||||
// blake3_hasher_update(&hasher, v.data(), v.size());
|
||||
if (input_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t *input_bytes = (const uint8_t *)input;
|
||||
|
||||
// If we have some partial chunk bytes in the internal chunk_state, we need
|
||||
// to finish that chunk first.
|
||||
if (chunk_state_len(&self->chunk) > 0) {
|
||||
size_t take = BLAKE3_CHUNK_LEN - chunk_state_len(&self->chunk);
|
||||
if (take > input_len) {
|
||||
take = input_len;
|
||||
}
|
||||
chunk_state_update(&self->chunk, input_bytes, take);
|
||||
input_bytes += take;
|
||||
input_len -= take;
|
||||
// If we've filled the current chunk and there's more coming, finalize this
|
||||
// chunk and proceed. In this case we know it's not the root.
|
||||
if (input_len > 0) {
|
||||
output_t output = chunk_state_output(&self->chunk);
|
||||
uint8_t chunk_cv[32];
|
||||
output_chaining_value(&output, chunk_cv);
|
||||
hasher_push_cv(self, chunk_cv, self->chunk.chunk_counter);
|
||||
chunk_state_reset(&self->chunk, self->key, self->chunk.chunk_counter + 1);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now the chunk_state is clear, and we have more input. If there's more than
|
||||
// a single chunk (so, definitely not the root chunk), hash the largest whole
|
||||
// subtree we can, with the full benefits of SIMD (and maybe in the future,
|
||||
// multi-threading) parallelism. Two restrictions:
|
||||
// - The subtree has to be a power-of-2 number of chunks. Only subtrees along
|
||||
// the right edge can be incomplete, and we don't know where the right edge
|
||||
// is going to be until we get to finalize().
|
||||
// - The subtree must evenly divide the total number of chunks up until this
|
||||
// point (if total is not 0). If the current incomplete subtree is only
|
||||
// waiting for 1 more chunk, we can't hash a subtree of 4 chunks. We have
|
||||
// to complete the current subtree first.
|
||||
// Because we might need to break up the input to form powers of 2, or to
|
||||
// evenly divide what we already have, this part runs in a loop.
|
||||
while (input_len > BLAKE3_CHUNK_LEN) {
|
||||
size_t subtree_len = round_down_to_power_of_2(input_len);
|
||||
uint64_t count_so_far = self->chunk.chunk_counter * BLAKE3_CHUNK_LEN;
|
||||
// Shrink the subtree_len until it evenly divides the count so far. We know
|
||||
// that subtree_len itself is a power of 2, so we can use a bitmasking
|
||||
// trick instead of an actual remainder operation. (Note that if the caller
|
||||
// consistently passes power-of-2 inputs of the same size, as is hopefully
|
||||
// typical, this loop condition will always fail, and subtree_len will
|
||||
// always be the full length of the input.)
|
||||
//
|
||||
// An aside: We don't have to shrink subtree_len quite this much. For
|
||||
// example, if count_so_far is 1, we could pass 2 chunks to
|
||||
// compress_subtree_to_parent_node. Since we'll get 2 CVs back, we'll still
|
||||
// get the right answer in the end, and we might get to use 2-way SIMD
|
||||
// parallelism. The problem with this optimization, is that it gets us
|
||||
// stuck always hashing 2 chunks. The total number of chunks will remain
|
||||
// odd, and we'll never graduate to higher degrees of parallelism. See
|
||||
// https://github.com/BLAKE3-team/BLAKE3/issues/69.
|
||||
while ((((uint64_t)(subtree_len - 1)) & count_so_far) != 0) {
|
||||
subtree_len /= 2;
|
||||
}
|
||||
// The shrunken subtree_len might now be 1 chunk long. If so, hash that one
|
||||
// chunk by itself. Otherwise, compress the subtree into a pair of CVs.
|
||||
uint64_t subtree_chunks = subtree_len / BLAKE3_CHUNK_LEN;
|
||||
if (subtree_len <= BLAKE3_CHUNK_LEN) {
|
||||
blake3_chunk_state chunk_state;
|
||||
chunk_state_init(&chunk_state, self->key, self->chunk.flags);
|
||||
chunk_state.chunk_counter = self->chunk.chunk_counter;
|
||||
chunk_state_update(&chunk_state, input_bytes, subtree_len);
|
||||
output_t output = chunk_state_output(&chunk_state);
|
||||
uint8_t cv[BLAKE3_OUT_LEN];
|
||||
output_chaining_value(&output, cv);
|
||||
hasher_push_cv(self, cv, chunk_state.chunk_counter);
|
||||
} else {
|
||||
// This is the high-performance happy path, though getting here depends
|
||||
// on the caller giving us a long enough input.
|
||||
uint8_t cv_pair[2 * BLAKE3_OUT_LEN];
|
||||
compress_subtree_to_parent_node(input_bytes, subtree_len, self->key,
|
||||
self->chunk.chunk_counter,
|
||||
self->chunk.flags, cv_pair);
|
||||
hasher_push_cv(self, cv_pair, self->chunk.chunk_counter);
|
||||
hasher_push_cv(self, &cv_pair[BLAKE3_OUT_LEN],
|
||||
self->chunk.chunk_counter + (subtree_chunks / 2));
|
||||
}
|
||||
self->chunk.chunk_counter += subtree_chunks;
|
||||
input_bytes += subtree_len;
|
||||
input_len -= subtree_len;
|
||||
}
|
||||
|
||||
// If there's any remaining input less than a full chunk, add it to the chunk
|
||||
// state. In that case, also do a final merge loop to make sure the subtree
|
||||
// stack doesn't contain any unmerged pairs. The remaining input means we
|
||||
// know these merges are non-root. This merge loop isn't strictly necessary
|
||||
// here, because hasher_push_chunk_cv already does its own merge loop, but it
|
||||
// simplifies blake3_hasher_finalize below.
|
||||
if (input_len > 0) {
|
||||
chunk_state_update(&self->chunk, input_bytes, input_len);
|
||||
hasher_merge_cv_stack(self, self->chunk.chunk_counter);
|
||||
}
|
||||
}
|
||||
|
||||
void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,
|
||||
size_t out_len) {
|
||||
blake3_hasher_finalize_seek(self, 0, out, out_len);
|
||||
}
|
||||
|
||||
void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
|
||||
uint8_t *out, size_t out_len) {
|
||||
// Explicitly checking for zero avoids causing UB by passing a null pointer
|
||||
// to memcpy. This comes up in practice with things like:
|
||||
// std::vector<uint8_t> v;
|
||||
// blake3_hasher_finalize(&hasher, v.data(), v.size());
|
||||
if (out_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the subtree stack is empty, then the current chunk is the root.
|
||||
if (self->cv_stack_len == 0) {
|
||||
output_t output = chunk_state_output(&self->chunk);
|
||||
output_root_bytes(&output, seek, out, out_len);
|
||||
return;
|
||||
}
|
||||
// If there are any bytes in the chunk state, finalize that chunk and do a
|
||||
// roll-up merge between that chunk hash and every subtree in the stack. In
|
||||
// this case, the extra merge loop at the end of blake3_hasher_update
|
||||
// guarantees that none of the subtrees in the stack need to be merged with
|
||||
// each other first. Otherwise, if there are no bytes in the chunk state,
|
||||
// then the top of the stack is a chunk hash, and we start the merge from
|
||||
// that.
|
||||
output_t output;
|
||||
size_t cvs_remaining;
|
||||
if (chunk_state_len(&self->chunk) > 0) {
|
||||
cvs_remaining = self->cv_stack_len;
|
||||
output = chunk_state_output(&self->chunk);
|
||||
} else {
|
||||
// There are always at least 2 CVs in the stack in this case.
|
||||
cvs_remaining = self->cv_stack_len - 2;
|
||||
output = parent_output(&self->cv_stack[cvs_remaining * 32], self->key,
|
||||
self->chunk.flags);
|
||||
}
|
||||
while (cvs_remaining > 0) {
|
||||
cvs_remaining -= 1;
|
||||
uint8_t parent_block[BLAKE3_BLOCK_LEN];
|
||||
memcpy(parent_block, &self->cv_stack[cvs_remaining * 32], 32);
|
||||
output_chaining_value(&output, &parent_block[32]);
|
||||
output = parent_output(parent_block, self->key, self->chunk.flags);
|
||||
}
|
||||
output_root_bytes(&output, seek, out, out_len);
|
||||
}
|
||||
|
||||
void blake3_hasher_reset(blake3_hasher *self) {
|
||||
chunk_state_reset(&self->chunk, self->key, 0);
|
||||
self->cv_stack_len = 0;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#ifndef BLAKE3_H
|
||||
#define BLAKE3_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if !defined(BLAKE3_API)
|
||||
# if defined(_WIN32) || defined(__CYGWIN__)
|
||||
# if defined(BLAKE3_DLL)
|
||||
# if defined(BLAKE3_DLL_EXPORTS)
|
||||
# define BLAKE3_API __declspec(dllexport)
|
||||
# else
|
||||
# define BLAKE3_API __declspec(dllimport)
|
||||
# endif
|
||||
# define BLAKE3_PRIVATE
|
||||
# else
|
||||
# define BLAKE3_API
|
||||
# define BLAKE3_PRIVATE
|
||||
# endif
|
||||
# elif __GNUC__ >= 4
|
||||
# define BLAKE3_API __attribute__((visibility("default")))
|
||||
# define BLAKE3_PRIVATE __attribute__((visibility("hidden")))
|
||||
# else
|
||||
# define BLAKE3_API
|
||||
# define BLAKE3_PRIVATE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BLAKE3_VERSION_STRING "1.5.0"
|
||||
#define BLAKE3_KEY_LEN 32
|
||||
#define BLAKE3_OUT_LEN 32
|
||||
#define BLAKE3_BLOCK_LEN 64
|
||||
#define BLAKE3_CHUNK_LEN 1024
|
||||
#define BLAKE3_MAX_DEPTH 54
|
||||
|
||||
// This struct is a private implementation detail. It has to be here because
|
||||
// it's part of blake3_hasher below.
|
||||
typedef struct {
|
||||
uint32_t cv[8];
|
||||
uint64_t chunk_counter;
|
||||
uint8_t buf[BLAKE3_BLOCK_LEN];
|
||||
uint8_t buf_len;
|
||||
uint8_t blocks_compressed;
|
||||
uint8_t flags;
|
||||
} blake3_chunk_state;
|
||||
|
||||
typedef struct {
|
||||
uint32_t key[8];
|
||||
blake3_chunk_state chunk;
|
||||
uint8_t cv_stack_len;
|
||||
// The stack size is MAX_DEPTH + 1 because we do lazy merging. For example,
|
||||
// with 7 chunks, we have 3 entries in the stack. Adding an 8th chunk
|
||||
// requires a 4th entry, rather than merging everything down to 1, because we
|
||||
// don't know whether more input is coming. This is different from how the
|
||||
// reference implementation does things.
|
||||
uint8_t cv_stack[(BLAKE3_MAX_DEPTH + 1) * BLAKE3_OUT_LEN];
|
||||
} blake3_hasher;
|
||||
|
||||
BLAKE3_API const char *blake3_version(void);
|
||||
BLAKE3_API void blake3_hasher_init(blake3_hasher *self);
|
||||
BLAKE3_API void blake3_hasher_init_keyed(blake3_hasher *self,
|
||||
const uint8_t key[BLAKE3_KEY_LEN]);
|
||||
BLAKE3_API void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context);
|
||||
BLAKE3_API void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
|
||||
size_t context_len);
|
||||
BLAKE3_API void blake3_hasher_update(blake3_hasher *self, const void *input,
|
||||
size_t input_len);
|
||||
BLAKE3_API void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,
|
||||
size_t out_len);
|
||||
BLAKE3_API void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
|
||||
uint8_t *out, size_t out_len);
|
||||
BLAKE3_API void blake3_hasher_reset(blake3_hasher *self);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLAKE3_H */
|
||||
@@ -1,312 +0,0 @@
|
||||
#include "blake3_impl.h"
|
||||
|
||||
#include <immintrin.h>
|
||||
|
||||
#define DEGREE 8
|
||||
|
||||
INLINE __m256i loadu(const uint8_t src[32]) {
|
||||
return _mm256_loadu_si256((const __m256i *)src);
|
||||
}
|
||||
|
||||
INLINE void storeu(__m256i src, uint8_t dest[16]) {
|
||||
_mm256_storeu_si256((__m256i *)dest, src);
|
||||
}
|
||||
|
||||
INLINE __m256i addv(__m256i a, __m256i b) { return _mm256_add_epi32(a, b); }
|
||||
|
||||
// Note that clang-format doesn't like the name "xor" for some reason.
|
||||
INLINE __m256i xorv(__m256i a, __m256i b) { return _mm256_xor_si256(a, b); }
|
||||
|
||||
INLINE __m256i set1(uint32_t x) { return _mm256_set1_epi32((int32_t)x); }
|
||||
|
||||
INLINE __m256i rot16(__m256i x) {
|
||||
return _mm256_shuffle_epi8(
|
||||
x, _mm256_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2,
|
||||
13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2));
|
||||
}
|
||||
|
||||
INLINE __m256i rot12(__m256i x) {
|
||||
return _mm256_or_si256(_mm256_srli_epi32(x, 12), _mm256_slli_epi32(x, 32 - 12));
|
||||
}
|
||||
|
||||
INLINE __m256i rot8(__m256i x) {
|
||||
return _mm256_shuffle_epi8(
|
||||
x, _mm256_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1,
|
||||
12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1));
|
||||
}
|
||||
|
||||
INLINE __m256i rot7(__m256i x) {
|
||||
return _mm256_or_si256(_mm256_srli_epi32(x, 7), _mm256_slli_epi32(x, 32 - 7));
|
||||
}
|
||||
|
||||
INLINE void round_fn(__m256i v[16], __m256i m[16], size_t r) {
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);
|
||||
v[0] = addv(v[0], v[4]);
|
||||
v[1] = addv(v[1], v[5]);
|
||||
v[2] = addv(v[2], v[6]);
|
||||
v[3] = addv(v[3], v[7]);
|
||||
v[12] = xorv(v[12], v[0]);
|
||||
v[13] = xorv(v[13], v[1]);
|
||||
v[14] = xorv(v[14], v[2]);
|
||||
v[15] = xorv(v[15], v[3]);
|
||||
v[12] = rot16(v[12]);
|
||||
v[13] = rot16(v[13]);
|
||||
v[14] = rot16(v[14]);
|
||||
v[15] = rot16(v[15]);
|
||||
v[8] = addv(v[8], v[12]);
|
||||
v[9] = addv(v[9], v[13]);
|
||||
v[10] = addv(v[10], v[14]);
|
||||
v[11] = addv(v[11], v[15]);
|
||||
v[4] = xorv(v[4], v[8]);
|
||||
v[5] = xorv(v[5], v[9]);
|
||||
v[6] = xorv(v[6], v[10]);
|
||||
v[7] = xorv(v[7], v[11]);
|
||||
v[4] = rot12(v[4]);
|
||||
v[5] = rot12(v[5]);
|
||||
v[6] = rot12(v[6]);
|
||||
v[7] = rot12(v[7]);
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);
|
||||
v[0] = addv(v[0], v[4]);
|
||||
v[1] = addv(v[1], v[5]);
|
||||
v[2] = addv(v[2], v[6]);
|
||||
v[3] = addv(v[3], v[7]);
|
||||
v[12] = xorv(v[12], v[0]);
|
||||
v[13] = xorv(v[13], v[1]);
|
||||
v[14] = xorv(v[14], v[2]);
|
||||
v[15] = xorv(v[15], v[3]);
|
||||
v[12] = rot8(v[12]);
|
||||
v[13] = rot8(v[13]);
|
||||
v[14] = rot8(v[14]);
|
||||
v[15] = rot8(v[15]);
|
||||
v[8] = addv(v[8], v[12]);
|
||||
v[9] = addv(v[9], v[13]);
|
||||
v[10] = addv(v[10], v[14]);
|
||||
v[11] = addv(v[11], v[15]);
|
||||
v[4] = xorv(v[4], v[8]);
|
||||
v[5] = xorv(v[5], v[9]);
|
||||
v[6] = xorv(v[6], v[10]);
|
||||
v[7] = xorv(v[7], v[11]);
|
||||
v[4] = rot7(v[4]);
|
||||
v[5] = rot7(v[5]);
|
||||
v[6] = rot7(v[6]);
|
||||
v[7] = rot7(v[7]);
|
||||
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);
|
||||
v[0] = addv(v[0], v[5]);
|
||||
v[1] = addv(v[1], v[6]);
|
||||
v[2] = addv(v[2], v[7]);
|
||||
v[3] = addv(v[3], v[4]);
|
||||
v[15] = xorv(v[15], v[0]);
|
||||
v[12] = xorv(v[12], v[1]);
|
||||
v[13] = xorv(v[13], v[2]);
|
||||
v[14] = xorv(v[14], v[3]);
|
||||
v[15] = rot16(v[15]);
|
||||
v[12] = rot16(v[12]);
|
||||
v[13] = rot16(v[13]);
|
||||
v[14] = rot16(v[14]);
|
||||
v[10] = addv(v[10], v[15]);
|
||||
v[11] = addv(v[11], v[12]);
|
||||
v[8] = addv(v[8], v[13]);
|
||||
v[9] = addv(v[9], v[14]);
|
||||
v[5] = xorv(v[5], v[10]);
|
||||
v[6] = xorv(v[6], v[11]);
|
||||
v[7] = xorv(v[7], v[8]);
|
||||
v[4] = xorv(v[4], v[9]);
|
||||
v[5] = rot12(v[5]);
|
||||
v[6] = rot12(v[6]);
|
||||
v[7] = rot12(v[7]);
|
||||
v[4] = rot12(v[4]);
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);
|
||||
v[0] = addv(v[0], v[5]);
|
||||
v[1] = addv(v[1], v[6]);
|
||||
v[2] = addv(v[2], v[7]);
|
||||
v[3] = addv(v[3], v[4]);
|
||||
v[15] = xorv(v[15], v[0]);
|
||||
v[12] = xorv(v[12], v[1]);
|
||||
v[13] = xorv(v[13], v[2]);
|
||||
v[14] = xorv(v[14], v[3]);
|
||||
v[15] = rot8(v[15]);
|
||||
v[12] = rot8(v[12]);
|
||||
v[13] = rot8(v[13]);
|
||||
v[14] = rot8(v[14]);
|
||||
v[10] = addv(v[10], v[15]);
|
||||
v[11] = addv(v[11], v[12]);
|
||||
v[8] = addv(v[8], v[13]);
|
||||
v[9] = addv(v[9], v[14]);
|
||||
v[5] = xorv(v[5], v[10]);
|
||||
v[6] = xorv(v[6], v[11]);
|
||||
v[7] = xorv(v[7], v[8]);
|
||||
v[4] = xorv(v[4], v[9]);
|
||||
v[5] = rot7(v[5]);
|
||||
v[6] = rot7(v[6]);
|
||||
v[7] = rot7(v[7]);
|
||||
v[4] = rot7(v[4]);
|
||||
}
|
||||
|
||||
INLINE void transpose_vecs(__m256i vecs[DEGREE]) {
|
||||
// Interleave 32-bit lanes. The low unpack is lanes 00/11/44/55, and the high
|
||||
// is 22/33/66/77.
|
||||
__m256i ab_0145 = _mm256_unpacklo_epi32(vecs[0], vecs[1]);
|
||||
__m256i ab_2367 = _mm256_unpackhi_epi32(vecs[0], vecs[1]);
|
||||
__m256i cd_0145 = _mm256_unpacklo_epi32(vecs[2], vecs[3]);
|
||||
__m256i cd_2367 = _mm256_unpackhi_epi32(vecs[2], vecs[3]);
|
||||
__m256i ef_0145 = _mm256_unpacklo_epi32(vecs[4], vecs[5]);
|
||||
__m256i ef_2367 = _mm256_unpackhi_epi32(vecs[4], vecs[5]);
|
||||
__m256i gh_0145 = _mm256_unpacklo_epi32(vecs[6], vecs[7]);
|
||||
__m256i gh_2367 = _mm256_unpackhi_epi32(vecs[6], vecs[7]);
|
||||
|
||||
// Interleave 64-bit lanes. The low unpack is lanes 00/22 and the high is
|
||||
// 11/33.
|
||||
__m256i abcd_04 = _mm256_unpacklo_epi64(ab_0145, cd_0145);
|
||||
__m256i abcd_15 = _mm256_unpackhi_epi64(ab_0145, cd_0145);
|
||||
__m256i abcd_26 = _mm256_unpacklo_epi64(ab_2367, cd_2367);
|
||||
__m256i abcd_37 = _mm256_unpackhi_epi64(ab_2367, cd_2367);
|
||||
__m256i efgh_04 = _mm256_unpacklo_epi64(ef_0145, gh_0145);
|
||||
__m256i efgh_15 = _mm256_unpackhi_epi64(ef_0145, gh_0145);
|
||||
__m256i efgh_26 = _mm256_unpacklo_epi64(ef_2367, gh_2367);
|
||||
__m256i efgh_37 = _mm256_unpackhi_epi64(ef_2367, gh_2367);
|
||||
|
||||
// Interleave 128-bit lanes.
|
||||
vecs[0] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x20);
|
||||
vecs[1] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x20);
|
||||
vecs[2] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x20);
|
||||
vecs[3] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x20);
|
||||
vecs[4] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x31);
|
||||
vecs[5] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x31);
|
||||
vecs[6] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x31);
|
||||
vecs[7] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x31);
|
||||
}
|
||||
|
||||
INLINE void transpose_msg_vecs(const uint8_t *const *inputs,
|
||||
size_t block_offset, __m256i out[16]) {
|
||||
out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m256i)]);
|
||||
out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m256i)]);
|
||||
out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m256i)]);
|
||||
out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m256i)]);
|
||||
out[4] = loadu(&inputs[4][block_offset + 0 * sizeof(__m256i)]);
|
||||
out[5] = loadu(&inputs[5][block_offset + 0 * sizeof(__m256i)]);
|
||||
out[6] = loadu(&inputs[6][block_offset + 0 * sizeof(__m256i)]);
|
||||
out[7] = loadu(&inputs[7][block_offset + 0 * sizeof(__m256i)]);
|
||||
out[8] = loadu(&inputs[0][block_offset + 1 * sizeof(__m256i)]);
|
||||
out[9] = loadu(&inputs[1][block_offset + 1 * sizeof(__m256i)]);
|
||||
out[10] = loadu(&inputs[2][block_offset + 1 * sizeof(__m256i)]);
|
||||
out[11] = loadu(&inputs[3][block_offset + 1 * sizeof(__m256i)]);
|
||||
out[12] = loadu(&inputs[4][block_offset + 1 * sizeof(__m256i)]);
|
||||
out[13] = loadu(&inputs[5][block_offset + 1 * sizeof(__m256i)]);
|
||||
out[14] = loadu(&inputs[6][block_offset + 1 * sizeof(__m256i)]);
|
||||
out[15] = loadu(&inputs[7][block_offset + 1 * sizeof(__m256i)]);
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
_mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0);
|
||||
}
|
||||
transpose_vecs(&out[0]);
|
||||
transpose_vecs(&out[8]);
|
||||
}
|
||||
|
||||
INLINE void load_counters(uint64_t counter, bool increment_counter,
|
||||
__m256i *out_lo, __m256i *out_hi) {
|
||||
const __m256i mask = _mm256_set1_epi32(-(int32_t)increment_counter);
|
||||
const __m256i add0 = _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0);
|
||||
const __m256i add1 = _mm256_and_si256(mask, add0);
|
||||
__m256i l = _mm256_add_epi32(_mm256_set1_epi32((int32_t)counter), add1);
|
||||
__m256i carry = _mm256_cmpgt_epi32(_mm256_xor_si256(add1, _mm256_set1_epi32(0x80000000)),
|
||||
_mm256_xor_si256( l, _mm256_set1_epi32(0x80000000)));
|
||||
__m256i h = _mm256_sub_epi32(_mm256_set1_epi32((int32_t)(counter >> 32)), carry);
|
||||
*out_lo = l;
|
||||
*out_hi = h;
|
||||
}
|
||||
|
||||
static
|
||||
void blake3_hash8_avx2(const uint8_t *const *inputs, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out) {
|
||||
__m256i h_vecs[8] = {
|
||||
set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]),
|
||||
set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]),
|
||||
};
|
||||
__m256i counter_low_vec, counter_high_vec;
|
||||
load_counters(counter, increment_counter, &counter_low_vec,
|
||||
&counter_high_vec);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
|
||||
for (size_t block = 0; block < blocks; block++) {
|
||||
if (block + 1 == blocks) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
__m256i block_len_vec = set1(BLAKE3_BLOCK_LEN);
|
||||
__m256i block_flags_vec = set1(block_flags);
|
||||
__m256i msg_vecs[16];
|
||||
transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);
|
||||
|
||||
__m256i v[16] = {
|
||||
h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3],
|
||||
h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7],
|
||||
set1(IV[0]), set1(IV[1]), set1(IV[2]), set1(IV[3]),
|
||||
counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec,
|
||||
};
|
||||
round_fn(v, msg_vecs, 0);
|
||||
round_fn(v, msg_vecs, 1);
|
||||
round_fn(v, msg_vecs, 2);
|
||||
round_fn(v, msg_vecs, 3);
|
||||
round_fn(v, msg_vecs, 4);
|
||||
round_fn(v, msg_vecs, 5);
|
||||
round_fn(v, msg_vecs, 6);
|
||||
h_vecs[0] = xorv(v[0], v[8]);
|
||||
h_vecs[1] = xorv(v[1], v[9]);
|
||||
h_vecs[2] = xorv(v[2], v[10]);
|
||||
h_vecs[3] = xorv(v[3], v[11]);
|
||||
h_vecs[4] = xorv(v[4], v[12]);
|
||||
h_vecs[5] = xorv(v[5], v[13]);
|
||||
h_vecs[6] = xorv(v[6], v[14]);
|
||||
h_vecs[7] = xorv(v[7], v[15]);
|
||||
|
||||
block_flags = flags;
|
||||
}
|
||||
|
||||
transpose_vecs(h_vecs);
|
||||
storeu(h_vecs[0], &out[0 * sizeof(__m256i)]);
|
||||
storeu(h_vecs[1], &out[1 * sizeof(__m256i)]);
|
||||
storeu(h_vecs[2], &out[2 * sizeof(__m256i)]);
|
||||
storeu(h_vecs[3], &out[3 * sizeof(__m256i)]);
|
||||
storeu(h_vecs[4], &out[4 * sizeof(__m256i)]);
|
||||
storeu(h_vecs[5], &out[5 * sizeof(__m256i)]);
|
||||
storeu(h_vecs[6], &out[6 * sizeof(__m256i)]);
|
||||
storeu(h_vecs[7], &out[7 * sizeof(__m256i)]);
|
||||
}
|
||||
|
||||
void blake3_hash_many_avx2(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out) {
|
||||
while (num_inputs >= DEGREE) {
|
||||
blake3_hash8_avx2(inputs, blocks, key, counter, increment_counter, flags,
|
||||
flags_start, flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += DEGREE;
|
||||
}
|
||||
inputs += DEGREE;
|
||||
num_inputs -= DEGREE;
|
||||
out = &out[DEGREE * BLAKE3_OUT_LEN];
|
||||
}
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
blake3_hash_many_sse41(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end, out);
|
||||
#else
|
||||
blake3_hash_many_portable(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
#endif
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,305 +0,0 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "blake3_impl.h"
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if defined(_MSC_VER)
|
||||
#include <Windows.h>
|
||||
#include <intrin.h>
|
||||
#elif defined(__GNUC__)
|
||||
#include <immintrin.h>
|
||||
#else
|
||||
#undef IS_X86 /* Unimplemented! */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BLAKE3_ATOMICS)
|
||||
#if defined(__has_include)
|
||||
#if __has_include(<stdatomic.h>) && !defined(_MSC_VER)
|
||||
#define BLAKE3_ATOMICS 1
|
||||
#else
|
||||
#define BLAKE3_ATOMICS 0
|
||||
#endif /* __has_include(<stdatomic.h>) && !defined(_MSC_VER) */
|
||||
#else
|
||||
#define BLAKE3_ATOMICS 0
|
||||
#endif /* defined(__has_include) */
|
||||
#endif /* BLAKE3_ATOMICS */
|
||||
|
||||
#if BLAKE3_ATOMICS
|
||||
#define ATOMIC_INT _Atomic int
|
||||
#define ATOMIC_LOAD(x) x
|
||||
#define ATOMIC_STORE(x, y) x = y
|
||||
#elif defined(_MSC_VER)
|
||||
#define ATOMIC_INT LONG
|
||||
#define ATOMIC_LOAD(x) InterlockedOr(&x, 0)
|
||||
#define ATOMIC_STORE(x, y) InterlockedExchange(&x, y)
|
||||
#else
|
||||
#define ATOMIC_INT int
|
||||
#define ATOMIC_LOAD(x) x
|
||||
#define ATOMIC_STORE(x, y) x = y
|
||||
#endif
|
||||
|
||||
#define MAYBE_UNUSED(x) (void)((x))
|
||||
|
||||
#if defined(IS_X86)
|
||||
static uint64_t xgetbv(void) {
|
||||
#if defined(_MSC_VER)
|
||||
return _xgetbv(0);
|
||||
#else
|
||||
uint32_t eax = 0, edx = 0;
|
||||
__asm__ __volatile__("xgetbv\n" : "=a"(eax), "=d"(edx) : "c"(0));
|
||||
return ((uint64_t)edx << 32) | eax;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cpuid(uint32_t out[4], uint32_t id) {
|
||||
#if defined(_MSC_VER)
|
||||
__cpuid((int *)out, id);
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
__asm__ __volatile__("movl %%ebx, %1\n"
|
||||
"cpuid\n"
|
||||
"xchgl %1, %%ebx\n"
|
||||
: "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id));
|
||||
#else
|
||||
__asm__ __volatile__("cpuid\n"
|
||||
: "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cpuidex(uint32_t out[4], uint32_t id, uint32_t sid) {
|
||||
#if defined(_MSC_VER)
|
||||
__cpuidex((int *)out, id, sid);
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
__asm__ __volatile__("movl %%ebx, %1\n"
|
||||
"cpuid\n"
|
||||
"xchgl %1, %%ebx\n"
|
||||
: "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id), "c"(sid));
|
||||
#else
|
||||
__asm__ __volatile__("cpuid\n"
|
||||
: "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3])
|
||||
: "a"(id), "c"(sid));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
enum cpu_feature {
|
||||
SSE2 = 1 << 0,
|
||||
SSSE3 = 1 << 1,
|
||||
SSE41 = 1 << 2,
|
||||
AVX = 1 << 3,
|
||||
AVX2 = 1 << 4,
|
||||
AVX512F = 1 << 5,
|
||||
AVX512VL = 1 << 6,
|
||||
/* ... */
|
||||
UNDEFINED = 1 << 30
|
||||
};
|
||||
|
||||
#if !defined(BLAKE3_TESTING)
|
||||
static /* Allow the variable to be controlled manually for testing */
|
||||
#endif
|
||||
ATOMIC_INT g_cpu_features = UNDEFINED;
|
||||
|
||||
#if !defined(BLAKE3_TESTING)
|
||||
static
|
||||
#endif
|
||||
enum cpu_feature
|
||||
get_cpu_features(void) {
|
||||
|
||||
/* If TSAN detects a data race here, try compiling with -DBLAKE3_ATOMICS=1 */
|
||||
long features = ATOMIC_LOAD(g_cpu_features);
|
||||
if (features != UNDEFINED) {
|
||||
return (enum cpu_feature)features;
|
||||
} else {
|
||||
#if defined(IS_X86)
|
||||
uint32_t regs[4] = {0};
|
||||
uint32_t *eax = ®s[0], *ebx = ®s[1], *ecx = ®s[2], *edx = ®s[3];
|
||||
(void)edx;
|
||||
features = 0;
|
||||
cpuid(regs, 0);
|
||||
const int max_id = *eax;
|
||||
cpuid(regs, 1);
|
||||
#if defined(__amd64__) || defined(_M_X64)
|
||||
features |= SSE2;
|
||||
#else
|
||||
if (*edx & (1UL << 26))
|
||||
features |= SSE2;
|
||||
#endif
|
||||
if (*ecx & (1UL << 9))
|
||||
features |= SSSE3;
|
||||
if (*ecx & (1UL << 19))
|
||||
features |= SSE41;
|
||||
|
||||
if (*ecx & (1UL << 27)) { // OSXSAVE
|
||||
const uint64_t mask = xgetbv();
|
||||
if ((mask & 6) == 6) { // SSE and AVX states
|
||||
if (*ecx & (1UL << 28))
|
||||
features |= AVX;
|
||||
if (max_id >= 7) {
|
||||
cpuidex(regs, 7, 0);
|
||||
if (*ebx & (1UL << 5))
|
||||
features |= AVX2;
|
||||
if ((mask & 224) == 224) { // Opmask, ZMM_Hi256, Hi16_Zmm
|
||||
if (*ebx & (1UL << 31))
|
||||
features |= AVX512VL;
|
||||
if (*ebx & (1UL << 16))
|
||||
features |= AVX512F;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ATOMIC_STORE(g_cpu_features, features);
|
||||
return (enum cpu_feature)features;
|
||||
#else
|
||||
/* How to detect NEON? */
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void blake3_compress_in_place(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if (features & AVX512VL) {
|
||||
blake3_compress_in_place_avx512(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_compress_in_place_sse41(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_compress_in_place_sse2(cv, block, block_len, counter, flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
blake3_compress_in_place_portable(cv, block, block_len, counter, flags);
|
||||
}
|
||||
|
||||
void blake3_compress_xof(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t out[64]) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if (features & AVX512VL) {
|
||||
blake3_compress_xof_avx512(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_compress_xof_sse41(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_compress_xof_sse2(cv, block, block_len, counter, flags, out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
blake3_compress_xof_portable(cv, block, block_len, counter, flags, out);
|
||||
}
|
||||
|
||||
void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) {
|
||||
blake3_hash_many_avx512(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
if (features & AVX2) {
|
||||
blake3_hash_many_avx2(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
blake3_hash_many_sse41(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
blake3_hash_many_sse2(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
blake3_hash_many_neon(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end, out);
|
||||
return;
|
||||
#endif
|
||||
|
||||
blake3_hash_many_portable(inputs, num_inputs, blocks, key, counter,
|
||||
increment_counter, flags, flags_start, flags_end,
|
||||
out);
|
||||
}
|
||||
|
||||
// The dynamically detected SIMD degree of the current platform.
|
||||
size_t blake3_simd_degree(void) {
|
||||
#if defined(IS_X86)
|
||||
const enum cpu_feature features = get_cpu_features();
|
||||
MAYBE_UNUSED(features);
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) {
|
||||
return 16;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
if (features & AVX2) {
|
||||
return 8;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
if (features & SSE41) {
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
if (features & SSE2) {
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
return 4;
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
#ifndef BLAKE3_IMPL_H
|
||||
#define BLAKE3_IMPL_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "blake3.h"
|
||||
|
||||
// internal flags
|
||||
enum blake3_flags {
|
||||
CHUNK_START = 1 << 0,
|
||||
CHUNK_END = 1 << 1,
|
||||
PARENT = 1 << 2,
|
||||
ROOT = 1 << 3,
|
||||
KEYED_HASH = 1 << 4,
|
||||
DERIVE_KEY_CONTEXT = 1 << 5,
|
||||
DERIVE_KEY_MATERIAL = 1 << 6,
|
||||
};
|
||||
|
||||
// This C implementation tries to support recent versions of GCC, Clang, and
|
||||
// MSVC.
|
||||
#if defined(_MSC_VER)
|
||||
#define INLINE static __forceinline
|
||||
#else
|
||||
#define INLINE static inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define IS_X86
|
||||
#define IS_X86_64
|
||||
#endif
|
||||
|
||||
#if defined(__i386__) || defined(_M_IX86)
|
||||
#define IS_X86
|
||||
#define IS_X86_32
|
||||
#endif
|
||||
|
||||
#if defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define IS_AARCH64
|
||||
#endif
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(BLAKE3_USE_NEON)
|
||||
// If BLAKE3_USE_NEON not manually set, autodetect based on AArch64ness
|
||||
#if defined(IS_AARCH64)
|
||||
#if defined(__ARM_BIG_ENDIAN)
|
||||
#define BLAKE3_USE_NEON 0
|
||||
#else
|
||||
#define BLAKE3_USE_NEON 1
|
||||
#endif
|
||||
#else
|
||||
#define BLAKE3_USE_NEON 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(IS_X86)
|
||||
#define MAX_SIMD_DEGREE 16
|
||||
#elif BLAKE3_USE_NEON == 1
|
||||
#define MAX_SIMD_DEGREE 4
|
||||
#else
|
||||
#define MAX_SIMD_DEGREE 1
|
||||
#endif
|
||||
|
||||
// There are some places where we want a static size that's equal to the
|
||||
// MAX_SIMD_DEGREE, but also at least 2.
|
||||
#define MAX_SIMD_DEGREE_OR_2 (MAX_SIMD_DEGREE > 2 ? MAX_SIMD_DEGREE : 2)
|
||||
|
||||
static const uint32_t IV[8] = {0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL,
|
||||
0xA54FF53AUL, 0x510E527FUL, 0x9B05688CUL,
|
||||
0x1F83D9ABUL, 0x5BE0CD19UL};
|
||||
|
||||
static const uint8_t MSG_SCHEDULE[7][16] = {
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8},
|
||||
{3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1},
|
||||
{10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6},
|
||||
{12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4},
|
||||
{9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7},
|
||||
{11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13},
|
||||
};
|
||||
|
||||
/* Find index of the highest set bit */
|
||||
/* x is assumed to be nonzero. */
|
||||
static unsigned int highest_one(uint64_t x) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return 63 ^ (unsigned int)__builtin_clzll(x);
|
||||
#elif defined(_MSC_VER) && defined(IS_X86_64)
|
||||
unsigned long index;
|
||||
_BitScanReverse64(&index, x);
|
||||
return index;
|
||||
#elif defined(_MSC_VER) && defined(IS_X86_32)
|
||||
if(x >> 32) {
|
||||
unsigned long index;
|
||||
_BitScanReverse(&index, (unsigned long)(x >> 32));
|
||||
return 32 + index;
|
||||
} else {
|
||||
unsigned long index;
|
||||
_BitScanReverse(&index, (unsigned long)x);
|
||||
return index;
|
||||
}
|
||||
#else
|
||||
unsigned int c = 0;
|
||||
if(x & 0xffffffff00000000ULL) { x >>= 32; c += 32; }
|
||||
if(x & 0x00000000ffff0000ULL) { x >>= 16; c += 16; }
|
||||
if(x & 0x000000000000ff00ULL) { x >>= 8; c += 8; }
|
||||
if(x & 0x00000000000000f0ULL) { x >>= 4; c += 4; }
|
||||
if(x & 0x000000000000000cULL) { x >>= 2; c += 2; }
|
||||
if(x & 0x0000000000000002ULL) { c += 1; }
|
||||
return c;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of 1 bits.
|
||||
INLINE unsigned int popcnt(uint64_t x) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return (unsigned int)__builtin_popcountll(x);
|
||||
#else
|
||||
unsigned int count = 0;
|
||||
while (x != 0) {
|
||||
count += 1;
|
||||
x &= x - 1;
|
||||
}
|
||||
return count;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Largest power of two less than or equal to x. As a special case, returns 1
|
||||
// when x is 0.
|
||||
INLINE uint64_t round_down_to_power_of_2(uint64_t x) {
|
||||
return 1ULL << highest_one(x | 1);
|
||||
}
|
||||
|
||||
INLINE uint32_t counter_low(uint64_t counter) { return (uint32_t)counter; }
|
||||
|
||||
INLINE uint32_t counter_high(uint64_t counter) {
|
||||
return (uint32_t)(counter >> 32);
|
||||
}
|
||||
|
||||
INLINE uint32_t load32(const void *src) {
|
||||
const uint8_t *p = (const uint8_t *)src;
|
||||
return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) |
|
||||
((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24);
|
||||
}
|
||||
|
||||
INLINE void load_key_words(const uint8_t key[BLAKE3_KEY_LEN],
|
||||
uint32_t key_words[8]) {
|
||||
key_words[0] = load32(&key[0 * 4]);
|
||||
key_words[1] = load32(&key[1 * 4]);
|
||||
key_words[2] = load32(&key[2 * 4]);
|
||||
key_words[3] = load32(&key[3 * 4]);
|
||||
key_words[4] = load32(&key[4 * 4]);
|
||||
key_words[5] = load32(&key[5 * 4]);
|
||||
key_words[6] = load32(&key[6 * 4]);
|
||||
key_words[7] = load32(&key[7 * 4]);
|
||||
}
|
||||
|
||||
INLINE void store32(void *dst, uint32_t w) {
|
||||
uint8_t *p = (uint8_t *)dst;
|
||||
p[0] = (uint8_t)(w >> 0);
|
||||
p[1] = (uint8_t)(w >> 8);
|
||||
p[2] = (uint8_t)(w >> 16);
|
||||
p[3] = (uint8_t)(w >> 24);
|
||||
}
|
||||
|
||||
INLINE void store_cv_words(uint8_t bytes_out[32], uint32_t cv_words[8]) {
|
||||
store32(&bytes_out[0 * 4], cv_words[0]);
|
||||
store32(&bytes_out[1 * 4], cv_words[1]);
|
||||
store32(&bytes_out[2 * 4], cv_words[2]);
|
||||
store32(&bytes_out[3 * 4], cv_words[3]);
|
||||
store32(&bytes_out[4 * 4], cv_words[4]);
|
||||
store32(&bytes_out[5 * 4], cv_words[5]);
|
||||
store32(&bytes_out[6 * 4], cv_words[6]);
|
||||
store32(&bytes_out[7 * 4], cv_words[7]);
|
||||
}
|
||||
|
||||
void blake3_compress_in_place(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags,
|
||||
uint8_t out[64]);
|
||||
|
||||
void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out);
|
||||
|
||||
size_t blake3_simd_degree(void);
|
||||
|
||||
|
||||
// Declarations for implementation-specific functions.
|
||||
void blake3_compress_in_place_portable(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof_portable(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
|
||||
void blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
|
||||
#if defined(IS_X86)
|
||||
#if !defined(BLAKE3_NO_SSE2)
|
||||
void blake3_compress_in_place_sse2(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
void blake3_compress_xof_sse2(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
void blake3_hash_many_sse2(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_SSE41)
|
||||
void blake3_compress_in_place_sse41(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
void blake3_compress_xof_sse41(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
void blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX2)
|
||||
void blake3_hash_many_avx2(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#if !defined(BLAKE3_NO_AVX512)
|
||||
void blake3_compress_in_place_avx512(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
void blake3_compress_xof_avx512(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]);
|
||||
|
||||
void blake3_hash_many_avx512(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLAKE3_USE_NEON == 1
|
||||
void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out);
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* BLAKE3_IMPL_H */
|
||||
@@ -1,368 +0,0 @@
|
||||
#include "blake3_impl.h"
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
#ifdef __ARM_BIG_ENDIAN
|
||||
#error "This implementation only supports little-endian ARM."
|
||||
// It might be that all we need for big-endian support here is to get the loads
|
||||
// and stores right, but step zero would be finding a way to test it in CI.
|
||||
#endif
|
||||
|
||||
INLINE uint32x4_t loadu_128(const uint8_t src[16]) {
|
||||
// vld1q_u32 has alignment requirements. Don't use it.
|
||||
uint32x4_t x;
|
||||
memcpy(&x, src, 16);
|
||||
return x;
|
||||
}
|
||||
|
||||
INLINE void storeu_128(uint32x4_t src, uint8_t dest[16]) {
|
||||
// vst1q_u32 has alignment requirements. Don't use it.
|
||||
memcpy(dest, &src, 16);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t add_128(uint32x4_t a, uint32x4_t b) {
|
||||
return vaddq_u32(a, b);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t xor_128(uint32x4_t a, uint32x4_t b) {
|
||||
return veorq_u32(a, b);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t set1_128(uint32_t x) { return vld1q_dup_u32(&x); }
|
||||
|
||||
INLINE uint32x4_t set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
uint32_t array[4] = {a, b, c, d};
|
||||
return vld1q_u32(array);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t rot16_128(uint32x4_t x) {
|
||||
// The straightfoward implementation would be two shifts and an or, but that's
|
||||
// slower on microarchitectures we've tested. See
|
||||
// https://github.com/BLAKE3-team/BLAKE3/pull/319.
|
||||
// return vorrq_u32(vshrq_n_u32(x, 16), vshlq_n_u32(x, 32 - 16));
|
||||
return vreinterpretq_u32_u16(vrev32q_u16(vreinterpretq_u16_u32(x)));
|
||||
}
|
||||
|
||||
INLINE uint32x4_t rot12_128(uint32x4_t x) {
|
||||
// See comment in rot16_128.
|
||||
// return vorrq_u32(vshrq_n_u32(x, 12), vshlq_n_u32(x, 32 - 12));
|
||||
return vsriq_n_u32(vshlq_n_u32(x, 32-12), x, 12);
|
||||
}
|
||||
|
||||
INLINE uint32x4_t rot8_128(uint32x4_t x) {
|
||||
// See comment in rot16_128.
|
||||
// return vorrq_u32(vshrq_n_u32(x, 8), vshlq_n_u32(x, 32 - 8));
|
||||
#if defined(__clang__)
|
||||
return vreinterpretq_u32_u8(__builtin_shufflevector(vreinterpretq_u8_u32(x), vreinterpretq_u8_u32(x), 1,2,3,0,5,6,7,4,9,10,11,8,13,14,15,12));
|
||||
#elif __GNUC__ * 10000 + __GNUC_MINOR__ * 100 >=40700
|
||||
static const uint8x16_t r8 = {1,2,3,0,5,6,7,4,9,10,11,8,13,14,15,12};
|
||||
return vreinterpretq_u32_u8(__builtin_shuffle(vreinterpretq_u8_u32(x), vreinterpretq_u8_u32(x), r8));
|
||||
#else
|
||||
return vsriq_n_u32(vshlq_n_u32(x, 32-8), x, 8);
|
||||
#endif
|
||||
}
|
||||
|
||||
INLINE uint32x4_t rot7_128(uint32x4_t x) {
|
||||
// See comment in rot16_128.
|
||||
// return vorrq_u32(vshrq_n_u32(x, 7), vshlq_n_u32(x, 32 - 7));
|
||||
return vsriq_n_u32(vshlq_n_u32(x, 32-7), x, 7);
|
||||
}
|
||||
|
||||
// TODO: compress_neon
|
||||
|
||||
// TODO: hash2_neon
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* hash4_neon
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
INLINE void round_fn4(uint32x4_t v[16], uint32x4_t m[16], size_t r) {
|
||||
v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);
|
||||
v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);
|
||||
v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);
|
||||
v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);
|
||||
v[0] = add_128(v[0], v[4]);
|
||||
v[1] = add_128(v[1], v[5]);
|
||||
v[2] = add_128(v[2], v[6]);
|
||||
v[3] = add_128(v[3], v[7]);
|
||||
v[12] = xor_128(v[12], v[0]);
|
||||
v[13] = xor_128(v[13], v[1]);
|
||||
v[14] = xor_128(v[14], v[2]);
|
||||
v[15] = xor_128(v[15], v[3]);
|
||||
v[12] = rot16_128(v[12]);
|
||||
v[13] = rot16_128(v[13]);
|
||||
v[14] = rot16_128(v[14]);
|
||||
v[15] = rot16_128(v[15]);
|
||||
v[8] = add_128(v[8], v[12]);
|
||||
v[9] = add_128(v[9], v[13]);
|
||||
v[10] = add_128(v[10], v[14]);
|
||||
v[11] = add_128(v[11], v[15]);
|
||||
v[4] = xor_128(v[4], v[8]);
|
||||
v[5] = xor_128(v[5], v[9]);
|
||||
v[6] = xor_128(v[6], v[10]);
|
||||
v[7] = xor_128(v[7], v[11]);
|
||||
v[4] = rot12_128(v[4]);
|
||||
v[5] = rot12_128(v[5]);
|
||||
v[6] = rot12_128(v[6]);
|
||||
v[7] = rot12_128(v[7]);
|
||||
v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);
|
||||
v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);
|
||||
v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);
|
||||
v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);
|
||||
v[0] = add_128(v[0], v[4]);
|
||||
v[1] = add_128(v[1], v[5]);
|
||||
v[2] = add_128(v[2], v[6]);
|
||||
v[3] = add_128(v[3], v[7]);
|
||||
v[12] = xor_128(v[12], v[0]);
|
||||
v[13] = xor_128(v[13], v[1]);
|
||||
v[14] = xor_128(v[14], v[2]);
|
||||
v[15] = xor_128(v[15], v[3]);
|
||||
v[12] = rot8_128(v[12]);
|
||||
v[13] = rot8_128(v[13]);
|
||||
v[14] = rot8_128(v[14]);
|
||||
v[15] = rot8_128(v[15]);
|
||||
v[8] = add_128(v[8], v[12]);
|
||||
v[9] = add_128(v[9], v[13]);
|
||||
v[10] = add_128(v[10], v[14]);
|
||||
v[11] = add_128(v[11], v[15]);
|
||||
v[4] = xor_128(v[4], v[8]);
|
||||
v[5] = xor_128(v[5], v[9]);
|
||||
v[6] = xor_128(v[6], v[10]);
|
||||
v[7] = xor_128(v[7], v[11]);
|
||||
v[4] = rot7_128(v[4]);
|
||||
v[5] = rot7_128(v[5]);
|
||||
v[6] = rot7_128(v[6]);
|
||||
v[7] = rot7_128(v[7]);
|
||||
|
||||
v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);
|
||||
v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);
|
||||
v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);
|
||||
v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);
|
||||
v[0] = add_128(v[0], v[5]);
|
||||
v[1] = add_128(v[1], v[6]);
|
||||
v[2] = add_128(v[2], v[7]);
|
||||
v[3] = add_128(v[3], v[4]);
|
||||
v[15] = xor_128(v[15], v[0]);
|
||||
v[12] = xor_128(v[12], v[1]);
|
||||
v[13] = xor_128(v[13], v[2]);
|
||||
v[14] = xor_128(v[14], v[3]);
|
||||
v[15] = rot16_128(v[15]);
|
||||
v[12] = rot16_128(v[12]);
|
||||
v[13] = rot16_128(v[13]);
|
||||
v[14] = rot16_128(v[14]);
|
||||
v[10] = add_128(v[10], v[15]);
|
||||
v[11] = add_128(v[11], v[12]);
|
||||
v[8] = add_128(v[8], v[13]);
|
||||
v[9] = add_128(v[9], v[14]);
|
||||
v[5] = xor_128(v[5], v[10]);
|
||||
v[6] = xor_128(v[6], v[11]);
|
||||
v[7] = xor_128(v[7], v[8]);
|
||||
v[4] = xor_128(v[4], v[9]);
|
||||
v[5] = rot12_128(v[5]);
|
||||
v[6] = rot12_128(v[6]);
|
||||
v[7] = rot12_128(v[7]);
|
||||
v[4] = rot12_128(v[4]);
|
||||
v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);
|
||||
v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);
|
||||
v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);
|
||||
v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);
|
||||
v[0] = add_128(v[0], v[5]);
|
||||
v[1] = add_128(v[1], v[6]);
|
||||
v[2] = add_128(v[2], v[7]);
|
||||
v[3] = add_128(v[3], v[4]);
|
||||
v[15] = xor_128(v[15], v[0]);
|
||||
v[12] = xor_128(v[12], v[1]);
|
||||
v[13] = xor_128(v[13], v[2]);
|
||||
v[14] = xor_128(v[14], v[3]);
|
||||
v[15] = rot8_128(v[15]);
|
||||
v[12] = rot8_128(v[12]);
|
||||
v[13] = rot8_128(v[13]);
|
||||
v[14] = rot8_128(v[14]);
|
||||
v[10] = add_128(v[10], v[15]);
|
||||
v[11] = add_128(v[11], v[12]);
|
||||
v[8] = add_128(v[8], v[13]);
|
||||
v[9] = add_128(v[9], v[14]);
|
||||
v[5] = xor_128(v[5], v[10]);
|
||||
v[6] = xor_128(v[6], v[11]);
|
||||
v[7] = xor_128(v[7], v[8]);
|
||||
v[4] = xor_128(v[4], v[9]);
|
||||
v[5] = rot7_128(v[5]);
|
||||
v[6] = rot7_128(v[6]);
|
||||
v[7] = rot7_128(v[7]);
|
||||
v[4] = rot7_128(v[4]);
|
||||
}
|
||||
|
||||
INLINE void transpose_vecs_128(uint32x4_t vecs[4]) {
|
||||
// Individually transpose the four 2x2 sub-matrices in each corner.
|
||||
uint32x4x2_t rows01 = vtrnq_u32(vecs[0], vecs[1]);
|
||||
uint32x4x2_t rows23 = vtrnq_u32(vecs[2], vecs[3]);
|
||||
|
||||
// Swap the top-right and bottom-left 2x2s (which just got transposed).
|
||||
vecs[0] =
|
||||
vcombine_u32(vget_low_u32(rows01.val[0]), vget_low_u32(rows23.val[0]));
|
||||
vecs[1] =
|
||||
vcombine_u32(vget_low_u32(rows01.val[1]), vget_low_u32(rows23.val[1]));
|
||||
vecs[2] =
|
||||
vcombine_u32(vget_high_u32(rows01.val[0]), vget_high_u32(rows23.val[0]));
|
||||
vecs[3] =
|
||||
vcombine_u32(vget_high_u32(rows01.val[1]), vget_high_u32(rows23.val[1]));
|
||||
}
|
||||
|
||||
INLINE void transpose_msg_vecs4(const uint8_t *const *inputs,
|
||||
size_t block_offset, uint32x4_t out[16]) {
|
||||
out[0] = loadu_128(&inputs[0][block_offset + 0 * sizeof(uint32x4_t)]);
|
||||
out[1] = loadu_128(&inputs[1][block_offset + 0 * sizeof(uint32x4_t)]);
|
||||
out[2] = loadu_128(&inputs[2][block_offset + 0 * sizeof(uint32x4_t)]);
|
||||
out[3] = loadu_128(&inputs[3][block_offset + 0 * sizeof(uint32x4_t)]);
|
||||
out[4] = loadu_128(&inputs[0][block_offset + 1 * sizeof(uint32x4_t)]);
|
||||
out[5] = loadu_128(&inputs[1][block_offset + 1 * sizeof(uint32x4_t)]);
|
||||
out[6] = loadu_128(&inputs[2][block_offset + 1 * sizeof(uint32x4_t)]);
|
||||
out[7] = loadu_128(&inputs[3][block_offset + 1 * sizeof(uint32x4_t)]);
|
||||
out[8] = loadu_128(&inputs[0][block_offset + 2 * sizeof(uint32x4_t)]);
|
||||
out[9] = loadu_128(&inputs[1][block_offset + 2 * sizeof(uint32x4_t)]);
|
||||
out[10] = loadu_128(&inputs[2][block_offset + 2 * sizeof(uint32x4_t)]);
|
||||
out[11] = loadu_128(&inputs[3][block_offset + 2 * sizeof(uint32x4_t)]);
|
||||
out[12] = loadu_128(&inputs[0][block_offset + 3 * sizeof(uint32x4_t)]);
|
||||
out[13] = loadu_128(&inputs[1][block_offset + 3 * sizeof(uint32x4_t)]);
|
||||
out[14] = loadu_128(&inputs[2][block_offset + 3 * sizeof(uint32x4_t)]);
|
||||
out[15] = loadu_128(&inputs[3][block_offset + 3 * sizeof(uint32x4_t)]);
|
||||
transpose_vecs_128(&out[0]);
|
||||
transpose_vecs_128(&out[4]);
|
||||
transpose_vecs_128(&out[8]);
|
||||
transpose_vecs_128(&out[12]);
|
||||
}
|
||||
|
||||
INLINE void load_counters4(uint64_t counter, bool increment_counter,
|
||||
uint32x4_t *out_low, uint32x4_t *out_high) {
|
||||
uint64_t mask = (increment_counter ? ~0 : 0);
|
||||
*out_low = set4(
|
||||
counter_low(counter + (mask & 0)), counter_low(counter + (mask & 1)),
|
||||
counter_low(counter + (mask & 2)), counter_low(counter + (mask & 3)));
|
||||
*out_high = set4(
|
||||
counter_high(counter + (mask & 0)), counter_high(counter + (mask & 1)),
|
||||
counter_high(counter + (mask & 2)), counter_high(counter + (mask & 3)));
|
||||
}
|
||||
|
||||
void blake3_hash4_neon(const uint8_t *const *inputs, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out) {
|
||||
uint32x4_t h_vecs[8] = {
|
||||
set1_128(key[0]), set1_128(key[1]), set1_128(key[2]), set1_128(key[3]),
|
||||
set1_128(key[4]), set1_128(key[5]), set1_128(key[6]), set1_128(key[7]),
|
||||
};
|
||||
uint32x4_t counter_low_vec, counter_high_vec;
|
||||
load_counters4(counter, increment_counter, &counter_low_vec,
|
||||
&counter_high_vec);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
|
||||
for (size_t block = 0; block < blocks; block++) {
|
||||
if (block + 1 == blocks) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
uint32x4_t block_len_vec = set1_128(BLAKE3_BLOCK_LEN);
|
||||
uint32x4_t block_flags_vec = set1_128(block_flags);
|
||||
uint32x4_t msg_vecs[16];
|
||||
transpose_msg_vecs4(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);
|
||||
|
||||
uint32x4_t v[16] = {
|
||||
h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3],
|
||||
h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7],
|
||||
set1_128(IV[0]), set1_128(IV[1]), set1_128(IV[2]), set1_128(IV[3]),
|
||||
counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec,
|
||||
};
|
||||
round_fn4(v, msg_vecs, 0);
|
||||
round_fn4(v, msg_vecs, 1);
|
||||
round_fn4(v, msg_vecs, 2);
|
||||
round_fn4(v, msg_vecs, 3);
|
||||
round_fn4(v, msg_vecs, 4);
|
||||
round_fn4(v, msg_vecs, 5);
|
||||
round_fn4(v, msg_vecs, 6);
|
||||
h_vecs[0] = xor_128(v[0], v[8]);
|
||||
h_vecs[1] = xor_128(v[1], v[9]);
|
||||
h_vecs[2] = xor_128(v[2], v[10]);
|
||||
h_vecs[3] = xor_128(v[3], v[11]);
|
||||
h_vecs[4] = xor_128(v[4], v[12]);
|
||||
h_vecs[5] = xor_128(v[5], v[13]);
|
||||
h_vecs[6] = xor_128(v[6], v[14]);
|
||||
h_vecs[7] = xor_128(v[7], v[15]);
|
||||
|
||||
block_flags = flags;
|
||||
}
|
||||
|
||||
transpose_vecs_128(&h_vecs[0]);
|
||||
transpose_vecs_128(&h_vecs[4]);
|
||||
// The first four vecs now contain the first half of each output, and the
|
||||
// second four vecs contain the second half of each output.
|
||||
storeu_128(h_vecs[0], &out[0 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[4], &out[1 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[1], &out[2 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[5], &out[3 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[2], &out[4 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[6], &out[5 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[3], &out[6 * sizeof(uint32x4_t)]);
|
||||
storeu_128(h_vecs[7], &out[7 * sizeof(uint32x4_t)]);
|
||||
}
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* hash_many_neon
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
void blake3_compress_in_place_portable(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags);
|
||||
|
||||
INLINE void hash_one_neon(const uint8_t *input, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
uint8_t flags, uint8_t flags_start, uint8_t flags_end,
|
||||
uint8_t out[BLAKE3_OUT_LEN]) {
|
||||
uint32_t cv[8];
|
||||
memcpy(cv, key, BLAKE3_KEY_LEN);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
while (blocks > 0) {
|
||||
if (blocks == 1) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
// TODO: Implement compress_neon. However note that according to
|
||||
// https://github.com/BLAKE2/BLAKE2/commit/7965d3e6e1b4193438b8d3a656787587d2579227,
|
||||
// compress_neon might not be any faster than compress_portable.
|
||||
blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter,
|
||||
block_flags);
|
||||
input = &input[BLAKE3_BLOCK_LEN];
|
||||
blocks -= 1;
|
||||
block_flags = flags;
|
||||
}
|
||||
memcpy(out, cv, BLAKE3_OUT_LEN);
|
||||
}
|
||||
|
||||
void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out) {
|
||||
while (num_inputs >= 4) {
|
||||
blake3_hash4_neon(inputs, blocks, key, counter, increment_counter, flags,
|
||||
flags_start, flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 4;
|
||||
}
|
||||
inputs += 4;
|
||||
num_inputs -= 4;
|
||||
out = &out[4 * BLAKE3_OUT_LEN];
|
||||
}
|
||||
while (num_inputs > 0) {
|
||||
hash_one_neon(inputs[0], blocks, key, counter, flags, flags_start,
|
||||
flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 1;
|
||||
}
|
||||
inputs += 1;
|
||||
num_inputs -= 1;
|
||||
out = &out[BLAKE3_OUT_LEN];
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
#include "blake3_impl.h"
|
||||
#include <string.h>
|
||||
|
||||
INLINE uint32_t rotr32(uint32_t w, uint32_t c) {
|
||||
return (w >> c) | (w << (32 - c));
|
||||
}
|
||||
|
||||
INLINE void g(uint32_t *state, size_t a, size_t b, size_t c, size_t d,
|
||||
uint32_t x, uint32_t y) {
|
||||
state[a] = state[a] + state[b] + x;
|
||||
state[d] = rotr32(state[d] ^ state[a], 16);
|
||||
state[c] = state[c] + state[d];
|
||||
state[b] = rotr32(state[b] ^ state[c], 12);
|
||||
state[a] = state[a] + state[b] + y;
|
||||
state[d] = rotr32(state[d] ^ state[a], 8);
|
||||
state[c] = state[c] + state[d];
|
||||
state[b] = rotr32(state[b] ^ state[c], 7);
|
||||
}
|
||||
|
||||
INLINE void round_fn(uint32_t state[16], const uint32_t *msg, size_t round) {
|
||||
// Select the message schedule based on the round.
|
||||
const uint8_t *schedule = MSG_SCHEDULE[round];
|
||||
|
||||
// Mix the columns.
|
||||
g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]);
|
||||
g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]);
|
||||
g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]);
|
||||
g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]);
|
||||
|
||||
// Mix the rows.
|
||||
g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]);
|
||||
g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]);
|
||||
g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]);
|
||||
g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);
|
||||
}
|
||||
|
||||
INLINE void compress_pre(uint32_t state[16], const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags) {
|
||||
uint32_t block_words[16];
|
||||
block_words[0] = load32(block + 4 * 0);
|
||||
block_words[1] = load32(block + 4 * 1);
|
||||
block_words[2] = load32(block + 4 * 2);
|
||||
block_words[3] = load32(block + 4 * 3);
|
||||
block_words[4] = load32(block + 4 * 4);
|
||||
block_words[5] = load32(block + 4 * 5);
|
||||
block_words[6] = load32(block + 4 * 6);
|
||||
block_words[7] = load32(block + 4 * 7);
|
||||
block_words[8] = load32(block + 4 * 8);
|
||||
block_words[9] = load32(block + 4 * 9);
|
||||
block_words[10] = load32(block + 4 * 10);
|
||||
block_words[11] = load32(block + 4 * 11);
|
||||
block_words[12] = load32(block + 4 * 12);
|
||||
block_words[13] = load32(block + 4 * 13);
|
||||
block_words[14] = load32(block + 4 * 14);
|
||||
block_words[15] = load32(block + 4 * 15);
|
||||
|
||||
state[0] = cv[0];
|
||||
state[1] = cv[1];
|
||||
state[2] = cv[2];
|
||||
state[3] = cv[3];
|
||||
state[4] = cv[4];
|
||||
state[5] = cv[5];
|
||||
state[6] = cv[6];
|
||||
state[7] = cv[7];
|
||||
state[8] = IV[0];
|
||||
state[9] = IV[1];
|
||||
state[10] = IV[2];
|
||||
state[11] = IV[3];
|
||||
state[12] = counter_low(counter);
|
||||
state[13] = counter_high(counter);
|
||||
state[14] = (uint32_t)block_len;
|
||||
state[15] = (uint32_t)flags;
|
||||
|
||||
round_fn(state, &block_words[0], 0);
|
||||
round_fn(state, &block_words[0], 1);
|
||||
round_fn(state, &block_words[0], 2);
|
||||
round_fn(state, &block_words[0], 3);
|
||||
round_fn(state, &block_words[0], 4);
|
||||
round_fn(state, &block_words[0], 5);
|
||||
round_fn(state, &block_words[0], 6);
|
||||
}
|
||||
|
||||
void blake3_compress_in_place_portable(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
uint32_t state[16];
|
||||
compress_pre(state, cv, block, block_len, counter, flags);
|
||||
cv[0] = state[0] ^ state[8];
|
||||
cv[1] = state[1] ^ state[9];
|
||||
cv[2] = state[2] ^ state[10];
|
||||
cv[3] = state[3] ^ state[11];
|
||||
cv[4] = state[4] ^ state[12];
|
||||
cv[5] = state[5] ^ state[13];
|
||||
cv[6] = state[6] ^ state[14];
|
||||
cv[7] = state[7] ^ state[15];
|
||||
}
|
||||
|
||||
void blake3_compress_xof_portable(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]) {
|
||||
uint32_t state[16];
|
||||
compress_pre(state, cv, block, block_len, counter, flags);
|
||||
|
||||
store32(&out[0 * 4], state[0] ^ state[8]);
|
||||
store32(&out[1 * 4], state[1] ^ state[9]);
|
||||
store32(&out[2 * 4], state[2] ^ state[10]);
|
||||
store32(&out[3 * 4], state[3] ^ state[11]);
|
||||
store32(&out[4 * 4], state[4] ^ state[12]);
|
||||
store32(&out[5 * 4], state[5] ^ state[13]);
|
||||
store32(&out[6 * 4], state[6] ^ state[14]);
|
||||
store32(&out[7 * 4], state[7] ^ state[15]);
|
||||
store32(&out[8 * 4], state[8] ^ cv[0]);
|
||||
store32(&out[9 * 4], state[9] ^ cv[1]);
|
||||
store32(&out[10 * 4], state[10] ^ cv[2]);
|
||||
store32(&out[11 * 4], state[11] ^ cv[3]);
|
||||
store32(&out[12 * 4], state[12] ^ cv[4]);
|
||||
store32(&out[13 * 4], state[13] ^ cv[5]);
|
||||
store32(&out[14 * 4], state[14] ^ cv[6]);
|
||||
store32(&out[15 * 4], state[15] ^ cv[7]);
|
||||
}
|
||||
|
||||
INLINE void hash_one_portable(const uint8_t *input, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {
|
||||
uint32_t cv[8];
|
||||
memcpy(cv, key, BLAKE3_KEY_LEN);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
while (blocks > 0) {
|
||||
if (blocks == 1) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter,
|
||||
block_flags);
|
||||
input = &input[BLAKE3_BLOCK_LEN];
|
||||
blocks -= 1;
|
||||
block_flags = flags;
|
||||
}
|
||||
store_cv_words(out, cv);
|
||||
}
|
||||
|
||||
void blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out) {
|
||||
while (num_inputs > 0) {
|
||||
hash_one_portable(inputs[0], blocks, key, counter, flags, flags_start,
|
||||
flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 1;
|
||||
}
|
||||
inputs += 1;
|
||||
num_inputs -= 1;
|
||||
out = &out[BLAKE3_OUT_LEN];
|
||||
}
|
||||
}
|
||||
@@ -1,566 +0,0 @@
|
||||
#include "blake3_impl.h"
|
||||
|
||||
#include <immintrin.h>
|
||||
|
||||
#define DEGREE 4
|
||||
|
||||
#define _mm_shuffle_ps2(a, b, c) \
|
||||
(_mm_castps_si128( \
|
||||
_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), (c))))
|
||||
|
||||
INLINE __m128i loadu(const uint8_t src[16]) {
|
||||
return _mm_loadu_si128((const __m128i *)src);
|
||||
}
|
||||
|
||||
INLINE void storeu(__m128i src, uint8_t dest[16]) {
|
||||
_mm_storeu_si128((__m128i *)dest, src);
|
||||
}
|
||||
|
||||
INLINE __m128i addv(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }
|
||||
|
||||
// Note that clang-format doesn't like the name "xor" for some reason.
|
||||
INLINE __m128i xorv(__m128i a, __m128i b) { return _mm_xor_si128(a, b); }
|
||||
|
||||
INLINE __m128i set1(uint32_t x) { return _mm_set1_epi32((int32_t)x); }
|
||||
|
||||
INLINE __m128i set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
return _mm_setr_epi32((int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d);
|
||||
}
|
||||
|
||||
INLINE __m128i rot16(__m128i x) {
|
||||
return _mm_shufflehi_epi16(_mm_shufflelo_epi16(x, 0xB1), 0xB1);
|
||||
}
|
||||
|
||||
INLINE __m128i rot12(__m128i x) {
|
||||
return xorv(_mm_srli_epi32(x, 12), _mm_slli_epi32(x, 32 - 12));
|
||||
}
|
||||
|
||||
INLINE __m128i rot8(__m128i x) {
|
||||
return xorv(_mm_srli_epi32(x, 8), _mm_slli_epi32(x, 32 - 8));
|
||||
}
|
||||
|
||||
INLINE __m128i rot7(__m128i x) {
|
||||
return xorv(_mm_srli_epi32(x, 7), _mm_slli_epi32(x, 32 - 7));
|
||||
}
|
||||
|
||||
INLINE void g1(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,
|
||||
__m128i m) {
|
||||
*row0 = addv(addv(*row0, m), *row1);
|
||||
*row3 = xorv(*row3, *row0);
|
||||
*row3 = rot16(*row3);
|
||||
*row2 = addv(*row2, *row3);
|
||||
*row1 = xorv(*row1, *row2);
|
||||
*row1 = rot12(*row1);
|
||||
}
|
||||
|
||||
INLINE void g2(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,
|
||||
__m128i m) {
|
||||
*row0 = addv(addv(*row0, m), *row1);
|
||||
*row3 = xorv(*row3, *row0);
|
||||
*row3 = rot8(*row3);
|
||||
*row2 = addv(*row2, *row3);
|
||||
*row1 = xorv(*row1, *row2);
|
||||
*row1 = rot7(*row1);
|
||||
}
|
||||
|
||||
// Note the optimization here of leaving row1 as the unrotated row, rather than
|
||||
// row0. All the message loads below are adjusted to compensate for this. See
|
||||
// discussion at https://github.com/sneves/blake2-avx2/pull/4
|
||||
INLINE void diagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {
|
||||
*row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(2, 1, 0, 3));
|
||||
*row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));
|
||||
*row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
}
|
||||
|
||||
INLINE void undiagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {
|
||||
*row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
*row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));
|
||||
*row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3));
|
||||
}
|
||||
|
||||
INLINE __m128i blend_epi16(__m128i a, __m128i b, const int16_t imm8) {
|
||||
const __m128i bits = _mm_set_epi16(0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01);
|
||||
__m128i mask = _mm_set1_epi16(imm8);
|
||||
mask = _mm_and_si128(mask, bits);
|
||||
mask = _mm_cmpeq_epi16(mask, bits);
|
||||
return _mm_or_si128(_mm_and_si128(mask, b), _mm_andnot_si128(mask, a));
|
||||
}
|
||||
|
||||
INLINE void compress_pre(__m128i rows[4], const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags) {
|
||||
rows[0] = loadu((uint8_t *)&cv[0]);
|
||||
rows[1] = loadu((uint8_t *)&cv[4]);
|
||||
rows[2] = set4(IV[0], IV[1], IV[2], IV[3]);
|
||||
rows[3] = set4(counter_low(counter), counter_high(counter),
|
||||
(uint32_t)block_len, (uint32_t)flags);
|
||||
|
||||
__m128i m0 = loadu(&block[sizeof(__m128i) * 0]);
|
||||
__m128i m1 = loadu(&block[sizeof(__m128i) * 1]);
|
||||
__m128i m2 = loadu(&block[sizeof(__m128i) * 2]);
|
||||
__m128i m3 = loadu(&block[sizeof(__m128i) * 3]);
|
||||
|
||||
__m128i t0, t1, t2, t3, tt;
|
||||
|
||||
// Round 1. The first round permutes the message words from the original
|
||||
// input order, into the groups that get mixed in parallel.
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(2, 0, 2, 0)); // 6 4 2 0
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 3, 1)); // 7 5 3 1
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(2, 0, 2, 0)); // 14 12 10 8
|
||||
t2 = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2, 1, 0, 3)); // 12 10 8 14
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 1, 3, 1)); // 15 13 11 9
|
||||
t3 = _mm_shuffle_epi32(t3, _MM_SHUFFLE(2, 1, 0, 3)); // 13 11 9 15
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 2. This round and all following rounds apply a fixed permutation
|
||||
// to the message words from the round before.
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 3
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 4
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 5
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 6
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 7
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
}
|
||||
|
||||
void blake3_compress_in_place_sse2(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
__m128i rows[4];
|
||||
compress_pre(rows, cv, block, block_len, counter, flags);
|
||||
storeu(xorv(rows[0], rows[2]), (uint8_t *)&cv[0]);
|
||||
storeu(xorv(rows[1], rows[3]), (uint8_t *)&cv[4]);
|
||||
}
|
||||
|
||||
void blake3_compress_xof_sse2(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]) {
|
||||
__m128i rows[4];
|
||||
compress_pre(rows, cv, block, block_len, counter, flags);
|
||||
storeu(xorv(rows[0], rows[2]), &out[0]);
|
||||
storeu(xorv(rows[1], rows[3]), &out[16]);
|
||||
storeu(xorv(rows[2], loadu((uint8_t *)&cv[0])), &out[32]);
|
||||
storeu(xorv(rows[3], loadu((uint8_t *)&cv[4])), &out[48]);
|
||||
}
|
||||
|
||||
INLINE void round_fn(__m128i v[16], __m128i m[16], size_t r) {
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);
|
||||
v[0] = addv(v[0], v[4]);
|
||||
v[1] = addv(v[1], v[5]);
|
||||
v[2] = addv(v[2], v[6]);
|
||||
v[3] = addv(v[3], v[7]);
|
||||
v[12] = xorv(v[12], v[0]);
|
||||
v[13] = xorv(v[13], v[1]);
|
||||
v[14] = xorv(v[14], v[2]);
|
||||
v[15] = xorv(v[15], v[3]);
|
||||
v[12] = rot16(v[12]);
|
||||
v[13] = rot16(v[13]);
|
||||
v[14] = rot16(v[14]);
|
||||
v[15] = rot16(v[15]);
|
||||
v[8] = addv(v[8], v[12]);
|
||||
v[9] = addv(v[9], v[13]);
|
||||
v[10] = addv(v[10], v[14]);
|
||||
v[11] = addv(v[11], v[15]);
|
||||
v[4] = xorv(v[4], v[8]);
|
||||
v[5] = xorv(v[5], v[9]);
|
||||
v[6] = xorv(v[6], v[10]);
|
||||
v[7] = xorv(v[7], v[11]);
|
||||
v[4] = rot12(v[4]);
|
||||
v[5] = rot12(v[5]);
|
||||
v[6] = rot12(v[6]);
|
||||
v[7] = rot12(v[7]);
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);
|
||||
v[0] = addv(v[0], v[4]);
|
||||
v[1] = addv(v[1], v[5]);
|
||||
v[2] = addv(v[2], v[6]);
|
||||
v[3] = addv(v[3], v[7]);
|
||||
v[12] = xorv(v[12], v[0]);
|
||||
v[13] = xorv(v[13], v[1]);
|
||||
v[14] = xorv(v[14], v[2]);
|
||||
v[15] = xorv(v[15], v[3]);
|
||||
v[12] = rot8(v[12]);
|
||||
v[13] = rot8(v[13]);
|
||||
v[14] = rot8(v[14]);
|
||||
v[15] = rot8(v[15]);
|
||||
v[8] = addv(v[8], v[12]);
|
||||
v[9] = addv(v[9], v[13]);
|
||||
v[10] = addv(v[10], v[14]);
|
||||
v[11] = addv(v[11], v[15]);
|
||||
v[4] = xorv(v[4], v[8]);
|
||||
v[5] = xorv(v[5], v[9]);
|
||||
v[6] = xorv(v[6], v[10]);
|
||||
v[7] = xorv(v[7], v[11]);
|
||||
v[4] = rot7(v[4]);
|
||||
v[5] = rot7(v[5]);
|
||||
v[6] = rot7(v[6]);
|
||||
v[7] = rot7(v[7]);
|
||||
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);
|
||||
v[0] = addv(v[0], v[5]);
|
||||
v[1] = addv(v[1], v[6]);
|
||||
v[2] = addv(v[2], v[7]);
|
||||
v[3] = addv(v[3], v[4]);
|
||||
v[15] = xorv(v[15], v[0]);
|
||||
v[12] = xorv(v[12], v[1]);
|
||||
v[13] = xorv(v[13], v[2]);
|
||||
v[14] = xorv(v[14], v[3]);
|
||||
v[15] = rot16(v[15]);
|
||||
v[12] = rot16(v[12]);
|
||||
v[13] = rot16(v[13]);
|
||||
v[14] = rot16(v[14]);
|
||||
v[10] = addv(v[10], v[15]);
|
||||
v[11] = addv(v[11], v[12]);
|
||||
v[8] = addv(v[8], v[13]);
|
||||
v[9] = addv(v[9], v[14]);
|
||||
v[5] = xorv(v[5], v[10]);
|
||||
v[6] = xorv(v[6], v[11]);
|
||||
v[7] = xorv(v[7], v[8]);
|
||||
v[4] = xorv(v[4], v[9]);
|
||||
v[5] = rot12(v[5]);
|
||||
v[6] = rot12(v[6]);
|
||||
v[7] = rot12(v[7]);
|
||||
v[4] = rot12(v[4]);
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);
|
||||
v[0] = addv(v[0], v[5]);
|
||||
v[1] = addv(v[1], v[6]);
|
||||
v[2] = addv(v[2], v[7]);
|
||||
v[3] = addv(v[3], v[4]);
|
||||
v[15] = xorv(v[15], v[0]);
|
||||
v[12] = xorv(v[12], v[1]);
|
||||
v[13] = xorv(v[13], v[2]);
|
||||
v[14] = xorv(v[14], v[3]);
|
||||
v[15] = rot8(v[15]);
|
||||
v[12] = rot8(v[12]);
|
||||
v[13] = rot8(v[13]);
|
||||
v[14] = rot8(v[14]);
|
||||
v[10] = addv(v[10], v[15]);
|
||||
v[11] = addv(v[11], v[12]);
|
||||
v[8] = addv(v[8], v[13]);
|
||||
v[9] = addv(v[9], v[14]);
|
||||
v[5] = xorv(v[5], v[10]);
|
||||
v[6] = xorv(v[6], v[11]);
|
||||
v[7] = xorv(v[7], v[8]);
|
||||
v[4] = xorv(v[4], v[9]);
|
||||
v[5] = rot7(v[5]);
|
||||
v[6] = rot7(v[6]);
|
||||
v[7] = rot7(v[7]);
|
||||
v[4] = rot7(v[4]);
|
||||
}
|
||||
|
||||
INLINE void transpose_vecs(__m128i vecs[DEGREE]) {
|
||||
// Interleave 32-bit lanes. The low unpack is lanes 00/11 and the high is
|
||||
// 22/33. Note that this doesn't split the vector into two lanes, as the
|
||||
// AVX2 counterparts do.
|
||||
__m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]);
|
||||
__m128i ab_23 = _mm_unpackhi_epi32(vecs[0], vecs[1]);
|
||||
__m128i cd_01 = _mm_unpacklo_epi32(vecs[2], vecs[3]);
|
||||
__m128i cd_23 = _mm_unpackhi_epi32(vecs[2], vecs[3]);
|
||||
|
||||
// Interleave 64-bit lanes.
|
||||
__m128i abcd_0 = _mm_unpacklo_epi64(ab_01, cd_01);
|
||||
__m128i abcd_1 = _mm_unpackhi_epi64(ab_01, cd_01);
|
||||
__m128i abcd_2 = _mm_unpacklo_epi64(ab_23, cd_23);
|
||||
__m128i abcd_3 = _mm_unpackhi_epi64(ab_23, cd_23);
|
||||
|
||||
vecs[0] = abcd_0;
|
||||
vecs[1] = abcd_1;
|
||||
vecs[2] = abcd_2;
|
||||
vecs[3] = abcd_3;
|
||||
}
|
||||
|
||||
INLINE void transpose_msg_vecs(const uint8_t *const *inputs,
|
||||
size_t block_offset, __m128i out[16]) {
|
||||
out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m128i)]);
|
||||
out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m128i)]);
|
||||
out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m128i)]);
|
||||
out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m128i)]);
|
||||
out[4] = loadu(&inputs[0][block_offset + 1 * sizeof(__m128i)]);
|
||||
out[5] = loadu(&inputs[1][block_offset + 1 * sizeof(__m128i)]);
|
||||
out[6] = loadu(&inputs[2][block_offset + 1 * sizeof(__m128i)]);
|
||||
out[7] = loadu(&inputs[3][block_offset + 1 * sizeof(__m128i)]);
|
||||
out[8] = loadu(&inputs[0][block_offset + 2 * sizeof(__m128i)]);
|
||||
out[9] = loadu(&inputs[1][block_offset + 2 * sizeof(__m128i)]);
|
||||
out[10] = loadu(&inputs[2][block_offset + 2 * sizeof(__m128i)]);
|
||||
out[11] = loadu(&inputs[3][block_offset + 2 * sizeof(__m128i)]);
|
||||
out[12] = loadu(&inputs[0][block_offset + 3 * sizeof(__m128i)]);
|
||||
out[13] = loadu(&inputs[1][block_offset + 3 * sizeof(__m128i)]);
|
||||
out[14] = loadu(&inputs[2][block_offset + 3 * sizeof(__m128i)]);
|
||||
out[15] = loadu(&inputs[3][block_offset + 3 * sizeof(__m128i)]);
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
_mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0);
|
||||
}
|
||||
transpose_vecs(&out[0]);
|
||||
transpose_vecs(&out[4]);
|
||||
transpose_vecs(&out[8]);
|
||||
transpose_vecs(&out[12]);
|
||||
}
|
||||
|
||||
INLINE void load_counters(uint64_t counter, bool increment_counter,
|
||||
__m128i *out_lo, __m128i *out_hi) {
|
||||
const __m128i mask = _mm_set1_epi32(-(int32_t)increment_counter);
|
||||
const __m128i add0 = _mm_set_epi32(3, 2, 1, 0);
|
||||
const __m128i add1 = _mm_and_si128(mask, add0);
|
||||
__m128i l = _mm_add_epi32(_mm_set1_epi32((int32_t)counter), add1);
|
||||
__m128i carry = _mm_cmpgt_epi32(_mm_xor_si128(add1, _mm_set1_epi32(0x80000000)),
|
||||
_mm_xor_si128( l, _mm_set1_epi32(0x80000000)));
|
||||
__m128i h = _mm_sub_epi32(_mm_set1_epi32((int32_t)(counter >> 32)), carry);
|
||||
*out_lo = l;
|
||||
*out_hi = h;
|
||||
}
|
||||
|
||||
static
|
||||
void blake3_hash4_sse2(const uint8_t *const *inputs, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out) {
|
||||
__m128i h_vecs[8] = {
|
||||
set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]),
|
||||
set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]),
|
||||
};
|
||||
__m128i counter_low_vec, counter_high_vec;
|
||||
load_counters(counter, increment_counter, &counter_low_vec,
|
||||
&counter_high_vec);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
|
||||
for (size_t block = 0; block < blocks; block++) {
|
||||
if (block + 1 == blocks) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
__m128i block_len_vec = set1(BLAKE3_BLOCK_LEN);
|
||||
__m128i block_flags_vec = set1(block_flags);
|
||||
__m128i msg_vecs[16];
|
||||
transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);
|
||||
|
||||
__m128i v[16] = {
|
||||
h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3],
|
||||
h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7],
|
||||
set1(IV[0]), set1(IV[1]), set1(IV[2]), set1(IV[3]),
|
||||
counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec,
|
||||
};
|
||||
round_fn(v, msg_vecs, 0);
|
||||
round_fn(v, msg_vecs, 1);
|
||||
round_fn(v, msg_vecs, 2);
|
||||
round_fn(v, msg_vecs, 3);
|
||||
round_fn(v, msg_vecs, 4);
|
||||
round_fn(v, msg_vecs, 5);
|
||||
round_fn(v, msg_vecs, 6);
|
||||
h_vecs[0] = xorv(v[0], v[8]);
|
||||
h_vecs[1] = xorv(v[1], v[9]);
|
||||
h_vecs[2] = xorv(v[2], v[10]);
|
||||
h_vecs[3] = xorv(v[3], v[11]);
|
||||
h_vecs[4] = xorv(v[4], v[12]);
|
||||
h_vecs[5] = xorv(v[5], v[13]);
|
||||
h_vecs[6] = xorv(v[6], v[14]);
|
||||
h_vecs[7] = xorv(v[7], v[15]);
|
||||
|
||||
block_flags = flags;
|
||||
}
|
||||
|
||||
transpose_vecs(&h_vecs[0]);
|
||||
transpose_vecs(&h_vecs[4]);
|
||||
// The first four vecs now contain the first half of each output, and the
|
||||
// second four vecs contain the second half of each output.
|
||||
storeu(h_vecs[0], &out[0 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[4], &out[1 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[1], &out[2 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[5], &out[3 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[2], &out[4 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[6], &out[5 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[3], &out[6 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[7], &out[7 * sizeof(__m128i)]);
|
||||
}
|
||||
|
||||
INLINE void hash_one_sse2(const uint8_t *input, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {
|
||||
uint32_t cv[8];
|
||||
memcpy(cv, key, BLAKE3_KEY_LEN);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
while (blocks > 0) {
|
||||
if (blocks == 1) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
blake3_compress_in_place_sse2(cv, input, BLAKE3_BLOCK_LEN, counter,
|
||||
block_flags);
|
||||
input = &input[BLAKE3_BLOCK_LEN];
|
||||
blocks -= 1;
|
||||
block_flags = flags;
|
||||
}
|
||||
memcpy(out, cv, BLAKE3_OUT_LEN);
|
||||
}
|
||||
|
||||
void blake3_hash_many_sse2(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out) {
|
||||
while (num_inputs >= DEGREE) {
|
||||
blake3_hash4_sse2(inputs, blocks, key, counter, increment_counter, flags,
|
||||
flags_start, flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += DEGREE;
|
||||
}
|
||||
inputs += DEGREE;
|
||||
num_inputs -= DEGREE;
|
||||
out = &out[DEGREE * BLAKE3_OUT_LEN];
|
||||
}
|
||||
while (num_inputs > 0) {
|
||||
hash_one_sse2(inputs[0], blocks, key, counter, flags, flags_start,
|
||||
flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 1;
|
||||
}
|
||||
inputs += 1;
|
||||
num_inputs -= 1;
|
||||
out = &out[BLAKE3_OUT_LEN];
|
||||
}
|
||||
}
|
||||
@@ -1,560 +0,0 @@
|
||||
#include "blake3_impl.h"
|
||||
|
||||
#include <immintrin.h>
|
||||
|
||||
#define DEGREE 4
|
||||
|
||||
#define _mm_shuffle_ps2(a, b, c) \
|
||||
(_mm_castps_si128( \
|
||||
_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), (c))))
|
||||
|
||||
INLINE __m128i loadu(const uint8_t src[16]) {
|
||||
return _mm_loadu_si128((const __m128i *)src);
|
||||
}
|
||||
|
||||
INLINE void storeu(__m128i src, uint8_t dest[16]) {
|
||||
_mm_storeu_si128((__m128i *)dest, src);
|
||||
}
|
||||
|
||||
INLINE __m128i addv(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }
|
||||
|
||||
// Note that clang-format doesn't like the name "xor" for some reason.
|
||||
INLINE __m128i xorv(__m128i a, __m128i b) { return _mm_xor_si128(a, b); }
|
||||
|
||||
INLINE __m128i set1(uint32_t x) { return _mm_set1_epi32((int32_t)x); }
|
||||
|
||||
INLINE __m128i set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
return _mm_setr_epi32((int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d);
|
||||
}
|
||||
|
||||
INLINE __m128i rot16(__m128i x) {
|
||||
return _mm_shuffle_epi8(
|
||||
x, _mm_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2));
|
||||
}
|
||||
|
||||
INLINE __m128i rot12(__m128i x) {
|
||||
return xorv(_mm_srli_epi32(x, 12), _mm_slli_epi32(x, 32 - 12));
|
||||
}
|
||||
|
||||
INLINE __m128i rot8(__m128i x) {
|
||||
return _mm_shuffle_epi8(
|
||||
x, _mm_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1));
|
||||
}
|
||||
|
||||
INLINE __m128i rot7(__m128i x) {
|
||||
return xorv(_mm_srli_epi32(x, 7), _mm_slli_epi32(x, 32 - 7));
|
||||
}
|
||||
|
||||
INLINE void g1(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,
|
||||
__m128i m) {
|
||||
*row0 = addv(addv(*row0, m), *row1);
|
||||
*row3 = xorv(*row3, *row0);
|
||||
*row3 = rot16(*row3);
|
||||
*row2 = addv(*row2, *row3);
|
||||
*row1 = xorv(*row1, *row2);
|
||||
*row1 = rot12(*row1);
|
||||
}
|
||||
|
||||
INLINE void g2(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,
|
||||
__m128i m) {
|
||||
*row0 = addv(addv(*row0, m), *row1);
|
||||
*row3 = xorv(*row3, *row0);
|
||||
*row3 = rot8(*row3);
|
||||
*row2 = addv(*row2, *row3);
|
||||
*row1 = xorv(*row1, *row2);
|
||||
*row1 = rot7(*row1);
|
||||
}
|
||||
|
||||
// Note the optimization here of leaving row1 as the unrotated row, rather than
|
||||
// row0. All the message loads below are adjusted to compensate for this. See
|
||||
// discussion at https://github.com/sneves/blake2-avx2/pull/4
|
||||
INLINE void diagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {
|
||||
*row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(2, 1, 0, 3));
|
||||
*row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));
|
||||
*row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
}
|
||||
|
||||
INLINE void undiagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {
|
||||
*row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
*row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));
|
||||
*row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3));
|
||||
}
|
||||
|
||||
INLINE void compress_pre(__m128i rows[4], const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter, uint8_t flags) {
|
||||
rows[0] = loadu((uint8_t *)&cv[0]);
|
||||
rows[1] = loadu((uint8_t *)&cv[4]);
|
||||
rows[2] = set4(IV[0], IV[1], IV[2], IV[3]);
|
||||
rows[3] = set4(counter_low(counter), counter_high(counter),
|
||||
(uint32_t)block_len, (uint32_t)flags);
|
||||
|
||||
__m128i m0 = loadu(&block[sizeof(__m128i) * 0]);
|
||||
__m128i m1 = loadu(&block[sizeof(__m128i) * 1]);
|
||||
__m128i m2 = loadu(&block[sizeof(__m128i) * 2]);
|
||||
__m128i m3 = loadu(&block[sizeof(__m128i) * 3]);
|
||||
|
||||
__m128i t0, t1, t2, t3, tt;
|
||||
|
||||
// Round 1. The first round permutes the message words from the original
|
||||
// input order, into the groups that get mixed in parallel.
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(2, 0, 2, 0)); // 6 4 2 0
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 3, 1)); // 7 5 3 1
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(2, 0, 2, 0)); // 14 12 10 8
|
||||
t2 = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2, 1, 0, 3)); // 12 10 8 14
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 1, 3, 1)); // 15 13 11 9
|
||||
t3 = _mm_shuffle_epi32(t3, _MM_SHUFFLE(2, 1, 0, 3)); // 13 11 9 15
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 2. This round and all following rounds apply a fixed permutation
|
||||
// to the message words from the round before.
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = _mm_blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = _mm_blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 3
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = _mm_blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = _mm_blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 4
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = _mm_blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = _mm_blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 5
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = _mm_blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = _mm_blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 6
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = _mm_blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = _mm_blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
m0 = t0;
|
||||
m1 = t1;
|
||||
m2 = t2;
|
||||
m3 = t3;
|
||||
|
||||
// Round 7
|
||||
t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));
|
||||
t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);
|
||||
t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));
|
||||
tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));
|
||||
t1 = _mm_blend_epi16(tt, t1, 0xCC);
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);
|
||||
diagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
t2 = _mm_unpacklo_epi64(m3, m1);
|
||||
tt = _mm_blend_epi16(t2, m2, 0xC0);
|
||||
t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));
|
||||
g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);
|
||||
t3 = _mm_unpackhi_epi32(m1, m3);
|
||||
tt = _mm_unpacklo_epi32(m2, t3);
|
||||
t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));
|
||||
g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);
|
||||
undiagonalize(&rows[0], &rows[2], &rows[3]);
|
||||
}
|
||||
|
||||
void blake3_compress_in_place_sse41(uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags) {
|
||||
__m128i rows[4];
|
||||
compress_pre(rows, cv, block, block_len, counter, flags);
|
||||
storeu(xorv(rows[0], rows[2]), (uint8_t *)&cv[0]);
|
||||
storeu(xorv(rows[1], rows[3]), (uint8_t *)&cv[4]);
|
||||
}
|
||||
|
||||
void blake3_compress_xof_sse41(const uint32_t cv[8],
|
||||
const uint8_t block[BLAKE3_BLOCK_LEN],
|
||||
uint8_t block_len, uint64_t counter,
|
||||
uint8_t flags, uint8_t out[64]) {
|
||||
__m128i rows[4];
|
||||
compress_pre(rows, cv, block, block_len, counter, flags);
|
||||
storeu(xorv(rows[0], rows[2]), &out[0]);
|
||||
storeu(xorv(rows[1], rows[3]), &out[16]);
|
||||
storeu(xorv(rows[2], loadu((uint8_t *)&cv[0])), &out[32]);
|
||||
storeu(xorv(rows[3], loadu((uint8_t *)&cv[4])), &out[48]);
|
||||
}
|
||||
|
||||
INLINE void round_fn(__m128i v[16], __m128i m[16], size_t r) {
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);
|
||||
v[0] = addv(v[0], v[4]);
|
||||
v[1] = addv(v[1], v[5]);
|
||||
v[2] = addv(v[2], v[6]);
|
||||
v[3] = addv(v[3], v[7]);
|
||||
v[12] = xorv(v[12], v[0]);
|
||||
v[13] = xorv(v[13], v[1]);
|
||||
v[14] = xorv(v[14], v[2]);
|
||||
v[15] = xorv(v[15], v[3]);
|
||||
v[12] = rot16(v[12]);
|
||||
v[13] = rot16(v[13]);
|
||||
v[14] = rot16(v[14]);
|
||||
v[15] = rot16(v[15]);
|
||||
v[8] = addv(v[8], v[12]);
|
||||
v[9] = addv(v[9], v[13]);
|
||||
v[10] = addv(v[10], v[14]);
|
||||
v[11] = addv(v[11], v[15]);
|
||||
v[4] = xorv(v[4], v[8]);
|
||||
v[5] = xorv(v[5], v[9]);
|
||||
v[6] = xorv(v[6], v[10]);
|
||||
v[7] = xorv(v[7], v[11]);
|
||||
v[4] = rot12(v[4]);
|
||||
v[5] = rot12(v[5]);
|
||||
v[6] = rot12(v[6]);
|
||||
v[7] = rot12(v[7]);
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);
|
||||
v[0] = addv(v[0], v[4]);
|
||||
v[1] = addv(v[1], v[5]);
|
||||
v[2] = addv(v[2], v[6]);
|
||||
v[3] = addv(v[3], v[7]);
|
||||
v[12] = xorv(v[12], v[0]);
|
||||
v[13] = xorv(v[13], v[1]);
|
||||
v[14] = xorv(v[14], v[2]);
|
||||
v[15] = xorv(v[15], v[3]);
|
||||
v[12] = rot8(v[12]);
|
||||
v[13] = rot8(v[13]);
|
||||
v[14] = rot8(v[14]);
|
||||
v[15] = rot8(v[15]);
|
||||
v[8] = addv(v[8], v[12]);
|
||||
v[9] = addv(v[9], v[13]);
|
||||
v[10] = addv(v[10], v[14]);
|
||||
v[11] = addv(v[11], v[15]);
|
||||
v[4] = xorv(v[4], v[8]);
|
||||
v[5] = xorv(v[5], v[9]);
|
||||
v[6] = xorv(v[6], v[10]);
|
||||
v[7] = xorv(v[7], v[11]);
|
||||
v[4] = rot7(v[4]);
|
||||
v[5] = rot7(v[5]);
|
||||
v[6] = rot7(v[6]);
|
||||
v[7] = rot7(v[7]);
|
||||
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);
|
||||
v[0] = addv(v[0], v[5]);
|
||||
v[1] = addv(v[1], v[6]);
|
||||
v[2] = addv(v[2], v[7]);
|
||||
v[3] = addv(v[3], v[4]);
|
||||
v[15] = xorv(v[15], v[0]);
|
||||
v[12] = xorv(v[12], v[1]);
|
||||
v[13] = xorv(v[13], v[2]);
|
||||
v[14] = xorv(v[14], v[3]);
|
||||
v[15] = rot16(v[15]);
|
||||
v[12] = rot16(v[12]);
|
||||
v[13] = rot16(v[13]);
|
||||
v[14] = rot16(v[14]);
|
||||
v[10] = addv(v[10], v[15]);
|
||||
v[11] = addv(v[11], v[12]);
|
||||
v[8] = addv(v[8], v[13]);
|
||||
v[9] = addv(v[9], v[14]);
|
||||
v[5] = xorv(v[5], v[10]);
|
||||
v[6] = xorv(v[6], v[11]);
|
||||
v[7] = xorv(v[7], v[8]);
|
||||
v[4] = xorv(v[4], v[9]);
|
||||
v[5] = rot12(v[5]);
|
||||
v[6] = rot12(v[6]);
|
||||
v[7] = rot12(v[7]);
|
||||
v[4] = rot12(v[4]);
|
||||
v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);
|
||||
v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);
|
||||
v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);
|
||||
v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);
|
||||
v[0] = addv(v[0], v[5]);
|
||||
v[1] = addv(v[1], v[6]);
|
||||
v[2] = addv(v[2], v[7]);
|
||||
v[3] = addv(v[3], v[4]);
|
||||
v[15] = xorv(v[15], v[0]);
|
||||
v[12] = xorv(v[12], v[1]);
|
||||
v[13] = xorv(v[13], v[2]);
|
||||
v[14] = xorv(v[14], v[3]);
|
||||
v[15] = rot8(v[15]);
|
||||
v[12] = rot8(v[12]);
|
||||
v[13] = rot8(v[13]);
|
||||
v[14] = rot8(v[14]);
|
||||
v[10] = addv(v[10], v[15]);
|
||||
v[11] = addv(v[11], v[12]);
|
||||
v[8] = addv(v[8], v[13]);
|
||||
v[9] = addv(v[9], v[14]);
|
||||
v[5] = xorv(v[5], v[10]);
|
||||
v[6] = xorv(v[6], v[11]);
|
||||
v[7] = xorv(v[7], v[8]);
|
||||
v[4] = xorv(v[4], v[9]);
|
||||
v[5] = rot7(v[5]);
|
||||
v[6] = rot7(v[6]);
|
||||
v[7] = rot7(v[7]);
|
||||
v[4] = rot7(v[4]);
|
||||
}
|
||||
|
||||
INLINE void transpose_vecs(__m128i vecs[DEGREE]) {
|
||||
// Interleave 32-bit lanes. The low unpack is lanes 00/11 and the high is
|
||||
// 22/33. Note that this doesn't split the vector into two lanes, as the
|
||||
// AVX2 counterparts do.
|
||||
__m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]);
|
||||
__m128i ab_23 = _mm_unpackhi_epi32(vecs[0], vecs[1]);
|
||||
__m128i cd_01 = _mm_unpacklo_epi32(vecs[2], vecs[3]);
|
||||
__m128i cd_23 = _mm_unpackhi_epi32(vecs[2], vecs[3]);
|
||||
|
||||
// Interleave 64-bit lanes.
|
||||
__m128i abcd_0 = _mm_unpacklo_epi64(ab_01, cd_01);
|
||||
__m128i abcd_1 = _mm_unpackhi_epi64(ab_01, cd_01);
|
||||
__m128i abcd_2 = _mm_unpacklo_epi64(ab_23, cd_23);
|
||||
__m128i abcd_3 = _mm_unpackhi_epi64(ab_23, cd_23);
|
||||
|
||||
vecs[0] = abcd_0;
|
||||
vecs[1] = abcd_1;
|
||||
vecs[2] = abcd_2;
|
||||
vecs[3] = abcd_3;
|
||||
}
|
||||
|
||||
INLINE void transpose_msg_vecs(const uint8_t *const *inputs,
|
||||
size_t block_offset, __m128i out[16]) {
|
||||
out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m128i)]);
|
||||
out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m128i)]);
|
||||
out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m128i)]);
|
||||
out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m128i)]);
|
||||
out[4] = loadu(&inputs[0][block_offset + 1 * sizeof(__m128i)]);
|
||||
out[5] = loadu(&inputs[1][block_offset + 1 * sizeof(__m128i)]);
|
||||
out[6] = loadu(&inputs[2][block_offset + 1 * sizeof(__m128i)]);
|
||||
out[7] = loadu(&inputs[3][block_offset + 1 * sizeof(__m128i)]);
|
||||
out[8] = loadu(&inputs[0][block_offset + 2 * sizeof(__m128i)]);
|
||||
out[9] = loadu(&inputs[1][block_offset + 2 * sizeof(__m128i)]);
|
||||
out[10] = loadu(&inputs[2][block_offset + 2 * sizeof(__m128i)]);
|
||||
out[11] = loadu(&inputs[3][block_offset + 2 * sizeof(__m128i)]);
|
||||
out[12] = loadu(&inputs[0][block_offset + 3 * sizeof(__m128i)]);
|
||||
out[13] = loadu(&inputs[1][block_offset + 3 * sizeof(__m128i)]);
|
||||
out[14] = loadu(&inputs[2][block_offset + 3 * sizeof(__m128i)]);
|
||||
out[15] = loadu(&inputs[3][block_offset + 3 * sizeof(__m128i)]);
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
_mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0);
|
||||
}
|
||||
transpose_vecs(&out[0]);
|
||||
transpose_vecs(&out[4]);
|
||||
transpose_vecs(&out[8]);
|
||||
transpose_vecs(&out[12]);
|
||||
}
|
||||
|
||||
INLINE void load_counters(uint64_t counter, bool increment_counter,
|
||||
__m128i *out_lo, __m128i *out_hi) {
|
||||
const __m128i mask = _mm_set1_epi32(-(int32_t)increment_counter);
|
||||
const __m128i add0 = _mm_set_epi32(3, 2, 1, 0);
|
||||
const __m128i add1 = _mm_and_si128(mask, add0);
|
||||
__m128i l = _mm_add_epi32(_mm_set1_epi32((int32_t)counter), add1);
|
||||
__m128i carry = _mm_cmpgt_epi32(_mm_xor_si128(add1, _mm_set1_epi32(0x80000000)),
|
||||
_mm_xor_si128( l, _mm_set1_epi32(0x80000000)));
|
||||
__m128i h = _mm_sub_epi32(_mm_set1_epi32((int32_t)(counter >> 32)), carry);
|
||||
*out_lo = l;
|
||||
*out_hi = h;
|
||||
}
|
||||
|
||||
static
|
||||
void blake3_hash4_sse41(const uint8_t *const *inputs, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
bool increment_counter, uint8_t flags,
|
||||
uint8_t flags_start, uint8_t flags_end, uint8_t *out) {
|
||||
__m128i h_vecs[8] = {
|
||||
set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]),
|
||||
set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]),
|
||||
};
|
||||
__m128i counter_low_vec, counter_high_vec;
|
||||
load_counters(counter, increment_counter, &counter_low_vec,
|
||||
&counter_high_vec);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
|
||||
for (size_t block = 0; block < blocks; block++) {
|
||||
if (block + 1 == blocks) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
__m128i block_len_vec = set1(BLAKE3_BLOCK_LEN);
|
||||
__m128i block_flags_vec = set1(block_flags);
|
||||
__m128i msg_vecs[16];
|
||||
transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);
|
||||
|
||||
__m128i v[16] = {
|
||||
h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3],
|
||||
h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7],
|
||||
set1(IV[0]), set1(IV[1]), set1(IV[2]), set1(IV[3]),
|
||||
counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec,
|
||||
};
|
||||
round_fn(v, msg_vecs, 0);
|
||||
round_fn(v, msg_vecs, 1);
|
||||
round_fn(v, msg_vecs, 2);
|
||||
round_fn(v, msg_vecs, 3);
|
||||
round_fn(v, msg_vecs, 4);
|
||||
round_fn(v, msg_vecs, 5);
|
||||
round_fn(v, msg_vecs, 6);
|
||||
h_vecs[0] = xorv(v[0], v[8]);
|
||||
h_vecs[1] = xorv(v[1], v[9]);
|
||||
h_vecs[2] = xorv(v[2], v[10]);
|
||||
h_vecs[3] = xorv(v[3], v[11]);
|
||||
h_vecs[4] = xorv(v[4], v[12]);
|
||||
h_vecs[5] = xorv(v[5], v[13]);
|
||||
h_vecs[6] = xorv(v[6], v[14]);
|
||||
h_vecs[7] = xorv(v[7], v[15]);
|
||||
|
||||
block_flags = flags;
|
||||
}
|
||||
|
||||
transpose_vecs(&h_vecs[0]);
|
||||
transpose_vecs(&h_vecs[4]);
|
||||
// The first four vecs now contain the first half of each output, and the
|
||||
// second four vecs contain the second half of each output.
|
||||
storeu(h_vecs[0], &out[0 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[4], &out[1 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[1], &out[2 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[5], &out[3 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[2], &out[4 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[6], &out[5 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[3], &out[6 * sizeof(__m128i)]);
|
||||
storeu(h_vecs[7], &out[7 * sizeof(__m128i)]);
|
||||
}
|
||||
|
||||
INLINE void hash_one_sse41(const uint8_t *input, size_t blocks,
|
||||
const uint32_t key[8], uint64_t counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {
|
||||
uint32_t cv[8];
|
||||
memcpy(cv, key, BLAKE3_KEY_LEN);
|
||||
uint8_t block_flags = flags | flags_start;
|
||||
while (blocks > 0) {
|
||||
if (blocks == 1) {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
blake3_compress_in_place_sse41(cv, input, BLAKE3_BLOCK_LEN, counter,
|
||||
block_flags);
|
||||
input = &input[BLAKE3_BLOCK_LEN];
|
||||
blocks -= 1;
|
||||
block_flags = flags;
|
||||
}
|
||||
memcpy(out, cv, BLAKE3_OUT_LEN);
|
||||
}
|
||||
|
||||
void blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs,
|
||||
size_t blocks, const uint32_t key[8],
|
||||
uint64_t counter, bool increment_counter,
|
||||
uint8_t flags, uint8_t flags_start,
|
||||
uint8_t flags_end, uint8_t *out) {
|
||||
while (num_inputs >= DEGREE) {
|
||||
blake3_hash4_sse41(inputs, blocks, key, counter, increment_counter, flags,
|
||||
flags_start, flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += DEGREE;
|
||||
}
|
||||
inputs += DEGREE;
|
||||
num_inputs -= DEGREE;
|
||||
out = &out[DEGREE * BLAKE3_OUT_LEN];
|
||||
}
|
||||
while (num_inputs > 0) {
|
||||
hash_one_sse41(inputs[0], blocks, key, counter, flags, flags_start,
|
||||
flags_end, out);
|
||||
if (increment_counter) {
|
||||
counter += 1;
|
||||
}
|
||||
inputs += 1;
|
||||
num_inputs -= 1;
|
||||
out = &out[BLAKE3_OUT_LEN];
|
||||
}
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
/*
|
||||
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
|
||||
* MD5 Message-Digest Algorithm (RFC 1321).
|
||||
*
|
||||
* Homepage:
|
||||
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
|
||||
*
|
||||
* Author:
|
||||
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
|
||||
*
|
||||
* This software was written by Alexander Peslyak in 2001. No copyright is
|
||||
* claimed, and the software is hereby placed in the public domain.
|
||||
* In case this attempt to disclaim copyright and place the software in the
|
||||
* public domain is deemed null and void, then the software is
|
||||
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
|
||||
* general public under the following terms:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted.
|
||||
*
|
||||
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||
*
|
||||
* (This is a heavily cut-down "BSD license".)
|
||||
*
|
||||
* This differs from Colin Plumb's older public domain implementation in that
|
||||
* no exactly 32-bit integer data type is required (any 32-bit or wider
|
||||
* unsigned integer data type will do), there's no compile-time endianness
|
||||
* configuration, and the function prototypes match OpenSSL's. No code from
|
||||
* Colin Plumb's implementation has been reused; this comment merely compares
|
||||
* the properties of the two independent implementations.
|
||||
*
|
||||
* The primary goals of this implementation are portability and ease of use.
|
||||
* It is meant to be fast, but not as fast as possible. Some known
|
||||
* optimizations are not included to reduce source code size and avoid
|
||||
* compile-time configuration.
|
||||
*/
|
||||
|
||||
#ifndef HAVE_OPENSSL
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "md5.h"
|
||||
|
||||
/*
|
||||
* The basic MD5 functions.
|
||||
*
|
||||
* F and G are optimized compared to their RFC 1321 definitions for
|
||||
* architectures that lack an AND-NOT instruction, just like in Colin Plumb's
|
||||
* implementation.
|
||||
*/
|
||||
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
|
||||
#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
|
||||
#define H(x, y, z) (((x) ^ (y)) ^ (z))
|
||||
#define H2(x, y, z) ((x) ^ ((y) ^ (z)))
|
||||
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
|
||||
|
||||
/*
|
||||
* The MD5 transformation for all four rounds.
|
||||
*/
|
||||
#define STEP(f, a, b, c, d, x, t, s) \
|
||||
(a) += f((b), (c), (d)) + (x) + (t); \
|
||||
(a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
|
||||
(a) += (b);
|
||||
|
||||
/*
|
||||
* SET reads 4 input bytes in little-endian byte order and stores them in a
|
||||
* properly aligned word in host byte order.
|
||||
*
|
||||
* The check for little-endian architectures that tolerate unaligned memory
|
||||
* accesses is just an optimization. Nothing will break if it fails to detect
|
||||
* a suitable architecture.
|
||||
*
|
||||
* Unfortunately, this optimization may be a C strict aliasing rules violation
|
||||
* if the caller's data buffer has effective type that cannot be aliased by
|
||||
* MD5_u32plus. In practice, this problem may occur if these MD5 routines are
|
||||
* inlined into a calling function, or with future and dangerously advanced
|
||||
* link-time optimizations. For the time being, keeping these MD5 routines in
|
||||
* their own translation unit avoids the problem.
|
||||
*/
|
||||
#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
|
||||
#define SET(n) \
|
||||
(*(MD5_u32plus *)&ptr[(n) * 4])
|
||||
#define GET(n) \
|
||||
SET(n)
|
||||
#else
|
||||
#define SET(n) \
|
||||
(ctx->block[(n)] = \
|
||||
(MD5_u32plus)ptr[(n) * 4] | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \
|
||||
((MD5_u32plus)ptr[(n) * 4 + 3] << 24))
|
||||
#define GET(n) \
|
||||
(ctx->block[(n)])
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This processes one or more 64-byte data blocks, but does NOT update the bit
|
||||
* counters. There are no alignment requirements.
|
||||
*/
|
||||
static const void *body(MD5_CTX *ctx, const void *data, unsigned long size)
|
||||
{
|
||||
const unsigned char *ptr;
|
||||
MD5_u32plus a, b, c, d;
|
||||
MD5_u32plus saved_a, saved_b, saved_c, saved_d;
|
||||
|
||||
ptr = (const unsigned char *)data;
|
||||
|
||||
a = ctx->a;
|
||||
b = ctx->b;
|
||||
c = ctx->c;
|
||||
d = ctx->d;
|
||||
|
||||
do {
|
||||
saved_a = a;
|
||||
saved_b = b;
|
||||
saved_c = c;
|
||||
saved_d = d;
|
||||
|
||||
/* Round 1 */
|
||||
STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
|
||||
STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
|
||||
STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
|
||||
STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
|
||||
STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
|
||||
STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
|
||||
STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
|
||||
STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
|
||||
STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
|
||||
STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
|
||||
STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
|
||||
STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
|
||||
STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
|
||||
STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
|
||||
STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
|
||||
STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
|
||||
|
||||
/* Round 2 */
|
||||
STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
|
||||
STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
|
||||
STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
|
||||
STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
|
||||
STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
|
||||
STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
|
||||
STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
|
||||
STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
|
||||
STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
|
||||
STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
|
||||
STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
|
||||
STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
|
||||
STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
|
||||
STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
|
||||
STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
|
||||
STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
|
||||
|
||||
/* Round 3 */
|
||||
STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
|
||||
STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11)
|
||||
STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
|
||||
STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23)
|
||||
STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
|
||||
STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11)
|
||||
STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
|
||||
STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23)
|
||||
STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
|
||||
STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11)
|
||||
STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
|
||||
STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23)
|
||||
STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
|
||||
STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11)
|
||||
STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
|
||||
STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23)
|
||||
|
||||
/* Round 4 */
|
||||
STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
|
||||
STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
|
||||
STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
|
||||
STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
|
||||
STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
|
||||
STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
|
||||
STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
|
||||
STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
|
||||
STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
|
||||
STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
|
||||
STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
|
||||
STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
|
||||
STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
|
||||
STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
|
||||
STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
|
||||
STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
|
||||
|
||||
a += saved_a;
|
||||
b += saved_b;
|
||||
c += saved_c;
|
||||
d += saved_d;
|
||||
|
||||
ptr += 64;
|
||||
} while (size -= 64);
|
||||
|
||||
ctx->a = a;
|
||||
ctx->b = b;
|
||||
ctx->c = c;
|
||||
ctx->d = d;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void MD5_Init(MD5_CTX *ctx)
|
||||
{
|
||||
ctx->a = 0x67452301;
|
||||
ctx->b = 0xefcdab89;
|
||||
ctx->c = 0x98badcfe;
|
||||
ctx->d = 0x10325476;
|
||||
|
||||
ctx->lo = 0;
|
||||
ctx->hi = 0;
|
||||
}
|
||||
|
||||
void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size)
|
||||
{
|
||||
MD5_u32plus saved_lo;
|
||||
unsigned long used, available;
|
||||
|
||||
saved_lo = ctx->lo;
|
||||
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
|
||||
ctx->hi++;
|
||||
ctx->hi += size >> 29;
|
||||
|
||||
used = saved_lo & 0x3f;
|
||||
|
||||
if (used) {
|
||||
available = 64 - used;
|
||||
|
||||
if (size < available) {
|
||||
memcpy(&ctx->buffer[used], data, size);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&ctx->buffer[used], data, available);
|
||||
data = (const unsigned char *)data + available;
|
||||
size -= available;
|
||||
body(ctx, ctx->buffer, 64);
|
||||
}
|
||||
|
||||
if (size >= 64) {
|
||||
data = body(ctx, data, size & ~(unsigned long)0x3f);
|
||||
size &= 0x3f;
|
||||
}
|
||||
|
||||
memcpy(ctx->buffer, data, size);
|
||||
}
|
||||
|
||||
#define MD5_OUT(dst, src) \
|
||||
(dst)[0] = (unsigned char)(src); \
|
||||
(dst)[1] = (unsigned char)((src) >> 8); \
|
||||
(dst)[2] = (unsigned char)((src) >> 16); \
|
||||
(dst)[3] = (unsigned char)((src) >> 24);
|
||||
|
||||
void MD5_Final(unsigned char *result, MD5_CTX *ctx)
|
||||
{
|
||||
unsigned long used, available;
|
||||
|
||||
used = ctx->lo & 0x3f;
|
||||
|
||||
ctx->buffer[used++] = 0x80;
|
||||
|
||||
available = 64 - used;
|
||||
|
||||
if (available < 8) {
|
||||
memset(&ctx->buffer[used], 0, available);
|
||||
body(ctx, ctx->buffer, 64);
|
||||
used = 0;
|
||||
available = 64;
|
||||
}
|
||||
|
||||
memset(&ctx->buffer[used], 0, available - 8);
|
||||
|
||||
ctx->lo <<= 3;
|
||||
MD5_OUT(&ctx->buffer[56], ctx->lo)
|
||||
MD5_OUT(&ctx->buffer[60], ctx->hi)
|
||||
|
||||
body(ctx, ctx->buffer, 64);
|
||||
|
||||
MD5_OUT(&result[0], ctx->a)
|
||||
MD5_OUT(&result[4], ctx->b)
|
||||
MD5_OUT(&result[8], ctx->c)
|
||||
MD5_OUT(&result[12], ctx->d)
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
|
||||
#undef MD5_OUT
|
||||
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
|
||||
* MD5 Message-Digest Algorithm (RFC 1321).
|
||||
*
|
||||
* Homepage:
|
||||
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
|
||||
*
|
||||
* Author:
|
||||
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
|
||||
*
|
||||
* This software was written by Alexander Peslyak in 2001. No copyright is
|
||||
* claimed, and the software is hereby placed in the public domain.
|
||||
* In case this attempt to disclaim copyright and place the software in the
|
||||
* public domain is deemed null and void, then the software is
|
||||
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
|
||||
* general public under the following terms:
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted.
|
||||
*
|
||||
* There's ABSOLUTELY NO WARRANTY, express or implied.
|
||||
*
|
||||
* See md5.c for more information.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
#include <openssl/md5.h>
|
||||
#elif !defined(_MD5_H)
|
||||
#define _MD5_H
|
||||
|
||||
/* Any 32-bit or wider unsigned integer data type will do */
|
||||
typedef unsigned int MD5_u32plus;
|
||||
|
||||
typedef struct {
|
||||
MD5_u32plus lo, hi;
|
||||
MD5_u32plus a, b, c, d;
|
||||
unsigned char buffer[64];
|
||||
MD5_u32plus block[16];
|
||||
} MD5_CTX;
|
||||
|
||||
extern void MD5_Init(MD5_CTX *ctx);
|
||||
extern void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size);
|
||||
extern void MD5_Final(unsigned char *result, MD5_CTX *ctx);
|
||||
|
||||
#endif
|
||||
@@ -1,607 +0,0 @@
|
||||
// New radsort.
|
||||
|
||||
// To Use:
|
||||
// Create a less_than function and then call radsort.
|
||||
//
|
||||
// So, for an array of unsigned ints:
|
||||
//
|
||||
// RSFORCEINLINE int int_is_before( void * elementa, void * elementb )
|
||||
// {
|
||||
// return *(unsigned int*)elementa < *(unsigned int*)elementb;
|
||||
// }
|
||||
//
|
||||
// radsort( buffer, count, int_is_before, unsigned int ); // type of each element is the last parameter
|
||||
//
|
||||
// If you comparison function is very complicated, then you might try
|
||||
// dropping the RSFORCEINLINE.
|
||||
|
||||
#include <stddef.h> // for size_t
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define RSFORCEINLINE __forceinline __declspec(safebuffers)
|
||||
#define CompilerReset(ptr) __assume(ptr)
|
||||
#else
|
||||
#define RSFORCEINLINE __attribute__((always_inline))
|
||||
#define CompilerReset(ptr)
|
||||
#endif
|
||||
|
||||
#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(_M_AMD64) || defined(__SSE__) || defined(__SSE2__) || defined(USE_SSE)
|
||||
#include <xmmintrin.h>
|
||||
#define RS_PREFETCH( addr ) _mm_prefetch( (addr), 0 )
|
||||
#endif
|
||||
|
||||
// nonsense to make adding pointers a more convenient
|
||||
#define rsadd_ptr( ptr, ind ) (((char*)(ptr))+(ptrdiff_t)(ind))
|
||||
#define rssub_ptr( ptr, ind ) (((char*)(ptr))-(ptrdiff_t)(ind))
|
||||
#define rsadd_ptr_elements( ptr, ind ) rsadd_ptr( ptr, (ptrdiff_t)(ind)*(ptrdiff_t)element_size )
|
||||
#define rsdiff_ptr_elements( ptra, ptrb ) ( (size_t)(((char*)(ptra))-((char*)(ptrb))) / (size_t)element_size )
|
||||
|
||||
// this is the maximum size of struct that we treat as a "simple" struct
|
||||
typedef struct RS_MAX_SIMPLE_BUF { char b[32]; } RS_MAX_SIMPLE_BUF; // todo, 64-bit
|
||||
|
||||
|
||||
// ==============================================================================================================
|
||||
// swap and move utility functions
|
||||
typedef struct bytes64 { char b[64]; } bytes64; // copying with this turns into m512 moves (when arch is set)
|
||||
typedef struct bytes32 { char b[32]; } bytes32; // copying with this turns into m256 moves (when arch is set)
|
||||
typedef struct bytes16 { char b[16]; } bytes16; // copying with this turns into m128 moves
|
||||
typedef struct bytes8 { char b[8]; } bytes8;
|
||||
|
||||
static RSFORCEINLINE void radsortswapper( void * a, void * b, size_t size )
|
||||
{
|
||||
#define RSSWAPMEM(type) ( size >= sizeof(type) ) { type v = *(type const*)a; *(type*)a = *(type const*)b; *(type*)b = v; a=rsadd_ptr(a,sizeof(type)); b=rsadd_ptr(b,sizeof(type)); size -= sizeof(type); }
|
||||
|
||||
while RSSWAPMEM(bytes64);
|
||||
if RSSWAPMEM(bytes32);
|
||||
if RSSWAPMEM(bytes16);
|
||||
if RSSWAPMEM(bytes8);
|
||||
if RSSWAPMEM(int);
|
||||
if RSSWAPMEM(short);
|
||||
if RSSWAPMEM(char);
|
||||
|
||||
#undef RSSWAPMEM
|
||||
}
|
||||
|
||||
// since size is always constant, this big function compiles down to 4 to 12 instructions (for normal structs 4-6)
|
||||
static RSFORCEINLINE void radsortmover( void * a, void * b, size_t size )
|
||||
{
|
||||
#define RSMOVEMEM(type) ( size >= sizeof(type) ) { *(type*)a = *(type const*)b; a=rsadd_ptr(a,sizeof(type)); b=rsadd_ptr(b,sizeof(type)); size -= sizeof(type); }
|
||||
|
||||
while RSMOVEMEM(bytes64);
|
||||
if RSMOVEMEM(bytes32);
|
||||
if RSMOVEMEM(bytes16);
|
||||
if RSMOVEMEM(bytes8);
|
||||
if RSMOVEMEM(int);
|
||||
if RSMOVEMEM(short);
|
||||
if RSMOVEMEM(char);
|
||||
|
||||
#undef RSMOVEMEM
|
||||
}
|
||||
|
||||
// these macros generate tiny move/swap routines that don't go through the generic function above (mostly for debug build performance)
|
||||
#define RS_SIMPLE_SIZES _X(1) _X(2) _X(4) _X(8) _X(12) _X(16)
|
||||
#define rsmoverfunc( num ) static RSFORCEINLINE void radsortmover##num ( void * dest, void * src, size_t element_size ) { typedef struct rs { char x[num]; } rs; *(rs*)dest = *(rs*)src; }
|
||||
#define rsswapperfunc( num ) static RSFORCEINLINE void radsortswapper##num( void * a, void * b, size_t element_size ) { typedef struct rs { char x[num]; } rs; rs temp; temp = *(rs*)a; *(rs*)a = *(rs*)b; *(rs*)b = temp; }
|
||||
|
||||
#define _X rsmoverfunc
|
||||
RS_SIMPLE_SIZES
|
||||
#undef _X
|
||||
#define _X rsswapperfunc
|
||||
RS_SIMPLE_SIZES
|
||||
#undef _X
|
||||
|
||||
#undef RS_SIMPLE_SIZES
|
||||
#undef rsmoverfunc
|
||||
#undef rsswapperfunc
|
||||
|
||||
|
||||
// ==============================================================================================================
|
||||
|
||||
typedef int is_before_func( void * elementa, void * elementb );
|
||||
typedef void swap_func( void * elementa, void * elementb, size_t element_size );
|
||||
typedef void move_func( void * dest, void * src, size_t size );
|
||||
typedef void rs_small_sort_func( void * left, size_t n, size_t element_size, is_before_func * is_before, move_func * mover, void * tmp );
|
||||
|
||||
#define radsortswapsize( size ) ( ( size == 1 ) ? radsortswapper1 : ( ( size == 2 ) ? radsortswapper2 : ( ( size == 4 ) ? radsortswapper4 : ( ( size == 8 ) ? radsortswapper8 : ( ( size == 12 ) ? radsortswapper12 : ( ( size == 16 ) ? radsortswapper16 : radsortswapper ) ) ) ) ) )
|
||||
#define radsortmovesize( size ) ( ( size == 1 ) ? radsortmover1 : ( ( size == 2 ) ? radsortmover2 : ( ( size == 4 ) ? radsortmover4 : ( ( size == 8 ) ? radsortmover8 : ( ( size == 12 ) ? radsortmover12 : ( ( size == 16 ) ? radsortmover16 : radsortmover ) ) ) ) ) )
|
||||
|
||||
// todo - maybe no bubble at all?
|
||||
#define RS_SMALL_FLIP_TO_INSERTION_GT_SIZE sizeof( size_t )
|
||||
typedef struct RS_MAX_BUBBLE_BUF { char b[RS_SMALL_FLIP_TO_INSERTION_GT_SIZE]; } RS_MAX_BUBBLE_BUF;
|
||||
|
||||
#define radsort( start, len, is_before_func ) \
|
||||
do { \
|
||||
char __rs_tmp[ sizeof( (start)[0] ) ]; \
|
||||
radsortinternal( start, len, sizeof( (start)[0] ), \
|
||||
is_before_func, \
|
||||
radsortswapsize( sizeof( (start)[0] ) ), \
|
||||
radsortmovesize( sizeof( (start)[0] ) ), \
|
||||
( sizeof( (start)[0] ) > RS_SMALL_FLIP_TO_INSERTION_GT_SIZE ) ? radinsertionsort : radbubble2sort, \
|
||||
( sizeof( (start)[0] ) > RS_SMALL_FLIP_TO_INSERTION_GT_SIZE ) ? RSS_FLIP_TO_SMALL_SORT_INSERTION : RSS_FLIP_TO_SMALL_SORT_BUBBLE2, \
|
||||
&__rs_tmp \
|
||||
); \
|
||||
} while (0)
|
||||
#define radheapsort( start, len, is_before_func ) do { radheapsortinteral( start, len, sizeof( ((start)[0]) ), is_before_func, radsortswapsize( sizeof( ((start)[0]) ) ) ); } while (0)
|
||||
|
||||
|
||||
//===================================================================================================
|
||||
// small heap sort - this sort is around 200 bytes compiled - can use directly when size is important
|
||||
|
||||
RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper )
|
||||
{
|
||||
void * left;
|
||||
void * right;
|
||||
size_t length;
|
||||
|
||||
left = start;
|
||||
right = rsadd_ptr_elements( start, len - 1 );
|
||||
length = len;
|
||||
|
||||
if ( length > 1 )
|
||||
{
|
||||
// unusual small in-place heap sort
|
||||
void * i; void * ind; void * v; void * n;
|
||||
size_t s, k;
|
||||
|
||||
s = length >> 1;
|
||||
i = rsadd_ptr_elements( left, s );
|
||||
|
||||
for(;;)
|
||||
{
|
||||
--s;
|
||||
i = rsadd_ptr_elements( i, -1 );
|
||||
ind = i;
|
||||
k = ( s << 1 ) + 1;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
v = rsadd_ptr_elements( left, k );
|
||||
n = rsadd_ptr_elements( v, 1 );
|
||||
|
||||
if ( ( ( n <= right ) ) && ( is_before( v, n ) ) )
|
||||
{
|
||||
++k;
|
||||
v = n;
|
||||
}
|
||||
|
||||
if ( is_before( ind, v ) )
|
||||
{
|
||||
swapper( ind, v, element_size );
|
||||
ind = v;
|
||||
k = ( k << 1 ) + 1;
|
||||
|
||||
if ( k < length )
|
||||
continue;
|
||||
}
|
||||
|
||||
// if s is non-zero, we are still building the heap!
|
||||
if ( s )
|
||||
break;
|
||||
|
||||
swapper( left, right, element_size );
|
||||
right = rsadd_ptr_elements( right, -1 );
|
||||
ind = left;
|
||||
k = 1;
|
||||
--length;
|
||||
|
||||
if ( length <= 1 )
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//===================================================================================================
|
||||
// median routines
|
||||
|
||||
#define rsswapsmaller( X, Y ) { RS_MAX_SIMPLE_BUF tmp; int cond; cond = is_before( &Y, &X); mover( &tmp, &X, element_size ); if ( cond ) mover( &X, &Y, element_size ); if ( cond ) mover( &Y, &tmp, element_size ); }
|
||||
|
||||
static RSFORCEINLINE void radsortgetmedian5( void * output, void * left, void * right, size_t length, size_t element_size, is_before_func * is_before, swap_func * swapper, move_func * mover )
|
||||
{
|
||||
RS_MAX_SIMPLE_BUF mb0,mb1,mb2,mb3,mb4;
|
||||
|
||||
mover( &mb0, left, element_size );
|
||||
mover( &mb1, rsadd_ptr_elements( left, length >> 2 ), element_size );
|
||||
mover( &mb2, rsadd_ptr_elements( left, length >> 1 ), element_size );
|
||||
mover( &mb3, rsadd_ptr_elements( left, length - (length >> 2) ), element_size );
|
||||
mover( &mb4, right, element_size );
|
||||
|
||||
// Basically, for simple compares, and for simple in-register types, this funcion
|
||||
// must turn info 7 compares and then 5-7 movs, and 12 cmovs. Any
|
||||
// compiler *should* do this - if this doesn't happen, then the compiler is
|
||||
// hosing you. You can put int 3s at the start and end of this function to check.
|
||||
|
||||
rsswapsmaller( mb0, mb1 );
|
||||
rsswapsmaller( mb2, mb3 );
|
||||
rsswapsmaller( mb0, mb2 );
|
||||
rsswapsmaller( mb1, mb3 );
|
||||
rsswapsmaller( mb1, mb4 );
|
||||
rsswapsmaller( mb1, mb2 );
|
||||
|
||||
mover( output, &mb2, element_size );
|
||||
if ( is_before( &mb4, &mb2 ) ) mover( output, &mb4, element_size );
|
||||
}
|
||||
|
||||
|
||||
static RSFORCEINLINE void radsortgetmedian9( void * output, void * left, void * right, size_t length, size_t element_size, is_before_func * is_before, swap_func * swapper, move_func * mover )
|
||||
{
|
||||
RS_MAX_SIMPLE_BUF mb0,mb1,mb2,mb3,mb4,mb5,mb6,mb7,mb8; // todo, temp mem!
|
||||
|
||||
#ifdef RS_PREFETCH
|
||||
RS_PREFETCH( left );
|
||||
RS_PREFETCH( right );
|
||||
RS_PREFETCH( rsadd_ptr_elements( left, length >> 3 ) );
|
||||
RS_PREFETCH( rsadd_ptr_elements( left, length >> 2 ) );
|
||||
RS_PREFETCH( rsadd_ptr_elements( left, (length >> 1) - (length >> 3) ) );
|
||||
RS_PREFETCH( rsadd_ptr_elements( left, length >> 1 ) );
|
||||
RS_PREFETCH( rsadd_ptr_elements( left, (length >> 1) + (0 >> 3) ) );
|
||||
RS_PREFETCH( rsadd_ptr_elements( left, length - (length >> 2) ) );
|
||||
RS_PREFETCH( rsadd_ptr_elements( left, length - (length >> 3) ) );
|
||||
#endif
|
||||
|
||||
mover( &mb0, left, element_size );
|
||||
mover( &mb1, rsadd_ptr_elements( left, length >> 3 ), element_size );
|
||||
mover( &mb2, rsadd_ptr_elements( left, length >> 2 ), element_size );
|
||||
mover( &mb3, rsadd_ptr_elements( left, (length >> 1) - (length >> 3) ), element_size );
|
||||
mover( &mb4, rsadd_ptr_elements( left, length >> 1 ), element_size );
|
||||
mover( &mb5, rsadd_ptr_elements( left, (length >> 1) + (length >> 3) ), element_size );
|
||||
mover( &mb6, rsadd_ptr_elements( left, length - (length >> 2) ), element_size );
|
||||
mover( &mb7, rsadd_ptr_elements( left, length - (length >> 3) ), element_size );
|
||||
mover( &mb8, right, element_size );
|
||||
|
||||
// Basically, for simple compares, and for simple in-register types, this funcion
|
||||
// should turn info 19 compares and then 15-19 movs, and 36 cmovs. However,
|
||||
// most compilers can only so-so job at this, and you'll end up with 3-4 jumps.
|
||||
// We just need cmov intrinsics.
|
||||
|
||||
rsswapsmaller( mb0, mb7 );
|
||||
rsswapsmaller( mb1, mb2 );
|
||||
rsswapsmaller( mb3, mb5 );
|
||||
rsswapsmaller( mb4, mb8 );
|
||||
rsswapsmaller( mb0, mb2 );
|
||||
rsswapsmaller( mb1, mb5 );
|
||||
rsswapsmaller( mb3, mb8 );
|
||||
rsswapsmaller( mb4, mb7 );
|
||||
rsswapsmaller( mb0, mb3 );
|
||||
rsswapsmaller( mb1, mb4 );
|
||||
rsswapsmaller( mb2, mb8 );
|
||||
rsswapsmaller( mb5, mb7 );
|
||||
rsswapsmaller( mb3, mb4 );
|
||||
rsswapsmaller( mb5, mb6 );
|
||||
rsswapsmaller( mb2, mb5 );
|
||||
rsswapsmaller( mb4, mb6 );
|
||||
rsswapsmaller( mb2, mb3 );
|
||||
rsswapsmaller( mb4, mb5 );
|
||||
|
||||
mover( output, &mb3, element_size );
|
||||
if ( is_before( &mb4, &mb3 ) ) mover( output, &mb4, element_size );
|
||||
}
|
||||
|
||||
#define RSS_USE_MEDIAN_9 1024
|
||||
|
||||
static RSFORCEINLINE void radsortgetmedian( void * output, void * left, void * right, size_t length, size_t element_size, is_before_func * is_before, swap_func * swapper, move_func * mover )
|
||||
{
|
||||
// get the median into copy
|
||||
if ( length >= RSS_USE_MEDIAN_9 )
|
||||
radsortgetmedian9( output, left, right, length, element_size, is_before, swapper, mover );
|
||||
else
|
||||
radsortgetmedian5( output, left, right, length, element_size, is_before, swapper, mover );
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===================================================================================================
|
||||
// bubble 2 routines - for partitions <= 16 count
|
||||
|
||||
// from Gerben Stavenga - bubble sort moving two values through at once
|
||||
// for ints, this compiles down to 38 instructions
|
||||
#define RSS_FLIP_TO_SMALL_SORT_BUBBLE2 16
|
||||
static RSFORCEINLINE void radbubble2sort( void * left, size_t n, size_t element_size, is_before_func * is_before, move_func * mover, void * tmp )
|
||||
{
|
||||
void * i; // todo - test with bigger blocks
|
||||
void * s = rsadd_ptr_elements( left, 2 );
|
||||
RS_MAX_BUBBLE_BUF x, y, z;
|
||||
|
||||
#define rsbubbleswap( X, Y ) { int cond; cond = is_before( &Y, &X); mover( tmp, &X, element_size ); if ( cond ) mover( &X, &Y, element_size ); if ( cond ) mover( &Y, tmp, element_size ); }
|
||||
|
||||
for ( i = rsadd_ptr_elements( left, (int)n - 1 ) ; i > left ; i = rsadd_ptr_elements( i, -2 ) )
|
||||
{
|
||||
void * j, * jm2;
|
||||
|
||||
// load x & y
|
||||
mover( &x, left, element_size );
|
||||
mover( &y, rsadd_ptr_elements( left, 1 ), element_size );
|
||||
|
||||
// swap x & y, so that x is smaller than y
|
||||
rsbubbleswap( x, y );
|
||||
|
||||
// for ints, this loop needs to be 4 cmps, 6 cmovs, and 5 movs
|
||||
// anything else will kill performance
|
||||
|
||||
jm2 = left;
|
||||
for ( j = s ; j <= i ; j = rsadd_ptr_elements( j, 1 ) )
|
||||
{
|
||||
// make z smaller than x and y, and the dump it to the left
|
||||
mover( &z, j, element_size );
|
||||
rsbubbleswap( z, x );
|
||||
rsbubbleswap( z, y );
|
||||
rsbubbleswap( x, y );
|
||||
mover( jm2, &z, element_size );
|
||||
jm2 = rsadd_ptr_elements( jm2, 1 );
|
||||
}
|
||||
|
||||
mover( rsadd_ptr_elements( i, -1 ), &x, element_size );
|
||||
mover( i, &y, element_size );
|
||||
}
|
||||
}
|
||||
|
||||
#define RSS_FLIP_TO_SMALL_SORT_INSERTION 28
|
||||
static RSFORCEINLINE void radinsertionsort(void * start, size_t len, size_t element_size, is_before_func * is_before, move_func * mover, void * tmp )
|
||||
{
|
||||
void * cur;
|
||||
void * prev;
|
||||
|
||||
cur = rsadd_ptr_elements( start, 1 );
|
||||
--len;
|
||||
prev = start;
|
||||
do
|
||||
{
|
||||
void * comp = cur;
|
||||
if ( is_before( comp, prev ) )
|
||||
{
|
||||
mover( tmp, comp, element_size );
|
||||
do
|
||||
{
|
||||
mover( comp, prev, element_size );
|
||||
comp = rsadd_ptr_elements( comp, -1 );
|
||||
if ( comp == start )
|
||||
break;
|
||||
prev = rsadd_ptr_elements( prev, -1 );
|
||||
} while ( is_before( tmp, prev ) );
|
||||
mover( comp, tmp, element_size );
|
||||
}
|
||||
prev = cur;
|
||||
cur = rsadd_ptr_elements( cur, 1 );
|
||||
} while( --len );
|
||||
}
|
||||
|
||||
/*
|
||||
todo
|
||||
static void * rs_start;
|
||||
static is_before_func * rs_ib;
|
||||
static size_t rs_es;
|
||||
|
||||
static RSFORCEINLINE int rss_byte_is_before_func( void * elementa, void * elementb )
|
||||
{
|
||||
unsigned char a = *(unsigned char*)elementa;
|
||||
unsigned char b = *(unsigned char*)elementb;
|
||||
size_t element_size = rs_es;
|
||||
return rs_ib( rsadd_ptr_elements( rs_start, a ), rsadd_ptr_elements( rs_start, b ) );
|
||||
}
|
||||
|
||||
|
||||
// do bubble sort of offsets, and THEN do all the swaps - faster on biy structures
|
||||
static RSFORCEINLINE void radsortbubble2offsets( void * left, size_t n, size_t element_size, is_before_func * is_before, swap_func * swapper, move_func * mover )
|
||||
{
|
||||
static unsigned char init[16] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
|
||||
unsigned char offsets[16];
|
||||
unsigned char swap[16];
|
||||
|
||||
radsortmover16( offsets, init, 16 );
|
||||
radsortmover16( swap, init, 16 );
|
||||
rs_start = left;
|
||||
rs_ib = is_before;
|
||||
rs_es = element_size;
|
||||
|
||||
// sort the byte offsets
|
||||
radsortbubble2( offsets, n, 1, rss_byte_is_before_func, radsortmover1 );
|
||||
|
||||
// now reorder the data
|
||||
{
|
||||
unsigned char i;
|
||||
void * ip = left;
|
||||
|
||||
for( i = 0 ; i < (unsigned char)n ; i++ )
|
||||
{
|
||||
unsigned char j = swap[ offsets[ i ] ];
|
||||
if ( i != j )
|
||||
{
|
||||
swapper( ip, rsadd_ptr_elements( left, j ), element_size );
|
||||
swap[ j ] = swap[ i ];
|
||||
}
|
||||
ip = rsadd_ptr_elements( ip, 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
//===================================================================================================
|
||||
|
||||
#undef rsswapsmaller
|
||||
|
||||
#define RSS_MAX_RECURSE 128
|
||||
|
||||
RSFORCEINLINE void radsortinternal( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper, move_func * mover, rs_small_sort_func * small_sort, size_t small_sort_thres, void * tmp )
|
||||
{
|
||||
void * left;
|
||||
size_t length;
|
||||
|
||||
if ( len <= 1 )
|
||||
return;
|
||||
|
||||
#if _DEBUG
|
||||
if ( element_size > sizeof( RS_MAX_SIMPLE_BUF ) )
|
||||
__debugbreak();
|
||||
#endif
|
||||
|
||||
// stack for no recursion
|
||||
typedef struct stks
|
||||
{
|
||||
void * left;
|
||||
size_t len;
|
||||
} stks;
|
||||
|
||||
stks stk[ RSS_MAX_RECURSE ];
|
||||
stks * stk_ptr = stk + RSS_MAX_RECURSE;
|
||||
|
||||
// we use the stk_ptr to tell when to flip to heap.
|
||||
// when we hit the end of the stack, we heap it, so
|
||||
// back the start of the stack to log1.5 of len
|
||||
length = len;
|
||||
do {
|
||||
--stk_ptr;
|
||||
if ( stk_ptr == stk ) { stk_ptr = stk+1; break; }
|
||||
length = ( length >> 1 ) + ( length >> 2 );
|
||||
} while ( length );
|
||||
stk_ptr[ -1 ].len = 0;
|
||||
|
||||
left = start;
|
||||
length = len;
|
||||
|
||||
do
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
// if tiny, hand with insertion
|
||||
if ( length <= small_sort_thres )
|
||||
{
|
||||
CompilerReset(left); // we reset the compiler before each major sort
|
||||
small_sort( left, length, element_size, is_before, mover, tmp );
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we have hit end of our recursion stack, flip to using a heap (this prevents N^2 behavior)
|
||||
if ( stk_ptr >= stk + RSS_MAX_RECURSE )
|
||||
{
|
||||
CompilerReset(left); // we reset the compiler before each major sort
|
||||
//printf("heap: %d\n",(int)length);
|
||||
radheapsortinteral( left, length, element_size, is_before, swapper );
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// partition
|
||||
void * rightequalpiv;
|
||||
size_t leftlen;
|
||||
void * scan, * piv, * rend, * right;
|
||||
|
||||
CompilerReset(left); // we reset the compiler before each major sort
|
||||
|
||||
right = rsadd_ptr_elements( left, length - 1 );
|
||||
|
||||
// check for and correct inverted blocks
|
||||
scan = left;
|
||||
rend = right;
|
||||
while ( is_before( rend, scan ) )
|
||||
{
|
||||
swapper( rend, scan, element_size );
|
||||
scan = rsadd_ptr_elements( scan, 1 );
|
||||
rend = rsadd_ptr_elements( rend, -1 );
|
||||
if ( scan >= rend ) break;
|
||||
}
|
||||
|
||||
// scan to see if the block is in order (or all the same)
|
||||
scan = left;
|
||||
do
|
||||
{
|
||||
void * next = rsadd_ptr( scan, element_size );
|
||||
if ( is_before( next, scan ) )
|
||||
goto doqsort;
|
||||
scan = next;
|
||||
} while ( scan < right );
|
||||
// if we get out of the loop cleanly, this block is already sorted, so just fall out and do next block
|
||||
break;
|
||||
|
||||
doqsort:
|
||||
|
||||
// get the median into copy
|
||||
radsortgetmedian( tmp, left, right, length, element_size, is_before, swapper, mover );
|
||||
|
||||
// if scan != left, then we have a few in order, so we can skip them all if the final is under the copy
|
||||
if ( !is_before( scan, tmp ) )
|
||||
scan = left;
|
||||
// this loop should be 3 instructions
|
||||
// skip values below the pivot at the start of the segment
|
||||
while( is_before( scan, tmp ) ) // the pivot will stop this loop
|
||||
scan = rsadd_ptr( scan, element_size );
|
||||
|
||||
// skip values above and equal to the pivot at the end of the segment
|
||||
rend = right;
|
||||
if ( left == start )
|
||||
{
|
||||
// we have to use this loop to check that we don't read off the front of
|
||||
// the array this loop should be 5 instructions
|
||||
while( rend > scan )
|
||||
{
|
||||
if ( !is_before( tmp, rend ) )
|
||||
break;
|
||||
rend = rsadd_ptr_elements( rend, -1 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we're not at the very start of the entire buffer, then we
|
||||
// can use this loop, which is only 3 instructions
|
||||
while( is_before( tmp, rend ) ) // the pivot will stop this loop
|
||||
rend = rsadd_ptr_elements( rend, -1 );
|
||||
}
|
||||
|
||||
// finally, do actual partitioning nanosort style - 65-70% of the
|
||||
// total time will be in this loop, for ints, this should be
|
||||
// 4 movs, 2 cmps, 1 cmov, 2 add, 1 jmp - 10 instructions
|
||||
// compilers getting this wrong is a 50-100% slowdown! You can
|
||||
// check the output by putting int 3s around this loop.
|
||||
CompilerReset(scan);
|
||||
piv = scan;
|
||||
while( scan <= rend )
|
||||
{
|
||||
size_t adv = is_before( scan, tmp );
|
||||
swapper( piv, scan, element_size );
|
||||
if ( adv ) piv = rsadd_ptr( piv, element_size ); // needs to be a cmov
|
||||
scan = rsadd_ptr( scan, element_size );
|
||||
}
|
||||
|
||||
// now move the right side to skip over all of the equal values...
|
||||
// this loop should be 5 instructions
|
||||
rightequalpiv = piv;
|
||||
while ( rightequalpiv < right )
|
||||
{
|
||||
if ( is_before( tmp, rightequalpiv ) )
|
||||
break;
|
||||
rightequalpiv = rsadd_ptr_elements( rightequalpiv, 1 );
|
||||
}
|
||||
|
||||
// ok, now get the size of each half and prepare to descend
|
||||
leftlen = rsdiff_ptr_elements( piv, left );
|
||||
length -= rsdiff_ptr_elements( rightequalpiv, left );
|
||||
|
||||
// put the smaller segment on the stack
|
||||
if ( length < leftlen )
|
||||
{
|
||||
// put small right on stack
|
||||
stk_ptr->left = rightequalpiv;
|
||||
stk_ptr->len = length;
|
||||
stk_ptr += ( length > 1 );
|
||||
length = leftlen;
|
||||
}
|
||||
else
|
||||
{
|
||||
// put small left on stack
|
||||
stk_ptr->left = left;
|
||||
stk_ptr->len = leftlen;
|
||||
stk_ptr += ( leftlen > 1 );
|
||||
left = rightequalpiv;
|
||||
if ( length <= 1 ) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--stk_ptr;
|
||||
left = stk_ptr->left;
|
||||
length = stk_ptr->len;
|
||||
} while ( length );
|
||||
}
|
||||
|
||||
#undef rsadd_ptr
|
||||
#undef rsadd_ptr_elements
|
||||
#undef rsdiff_ptr_elements
|
||||
@@ -1,26 +0,0 @@
|
||||
xxHash Library
|
||||
Copyright (c) 2012-2021 Yann Collet
|
||||
All rights reserved.
|
||||
|
||||
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,253 +0,0 @@
|
||||
|
||||
xxHash - Extremely fast hash algorithm
|
||||
======================================
|
||||
|
||||
xxHash is an Extremely fast Hash algorithm, processing at RAM speed limits.
|
||||
Code is highly portable, and produces hashes identical across all platforms (little / big endian).
|
||||
The library includes the following algorithms :
|
||||
- XXH32 : generates 32-bit hashes, using 32-bit arithmetic
|
||||
- XXH64 : generates 64-bit hashes, using 64-bit arithmetic
|
||||
- XXH3 (since `v0.8.0`): generates 64 or 128-bit hashes, using vectorized arithmetic.
|
||||
The 128-bit variant is called XXH128.
|
||||
|
||||
All variants successfully complete the [SMHasher](https://code.google.com/p/smhasher/wiki/SMHasher) test suite
|
||||
which evaluates the quality of hash functions (collision, dispersion and randomness).
|
||||
Additional tests, which evaluate more thoroughly speed and collision properties of 64-bit hashes, [are also provided](https://github.com/Cyan4973/xxHash/tree/dev/tests).
|
||||
|
||||
|Branch |Status |
|
||||
|------------|---------|
|
||||
|release | [](https://github.com/Cyan4973/xxHash/actions?query=branch%3Arelease+) |
|
||||
|dev | [](https://github.com/Cyan4973/xxHash/actions?query=branch%3Adev+) |
|
||||
|
||||
|
||||
Benchmarks
|
||||
-------------------------
|
||||
|
||||
The benchmarked reference system uses an Intel i7-9700K cpu, and runs Ubuntu x64 20.04.
|
||||
The [open source benchmark program] is compiled with `clang` v10.0 using `-O3` flag.
|
||||
|
||||
| Hash Name | Width | Bandwidth (GB/s) | Small Data Velocity | Quality | Comment |
|
||||
| --------- | ----- | ---------------- | ----- | --- | --- |
|
||||
| __XXH3__ (SSE2) | 64 | 31.5 GB/s | 133.1 | 10
|
||||
| __XXH128__ (SSE2) | 128 | 29.6 GB/s | 118.1 | 10
|
||||
| _RAM sequential read_ | N/A | 28.0 GB/s | N/A | N/A | _for reference_
|
||||
| City64 | 64 | 22.0 GB/s | 76.6 | 10
|
||||
| T1ha2 | 64 | 22.0 GB/s | 99.0 | 9 | Slightly worse [collisions]
|
||||
| City128 | 128 | 21.7 GB/s | 57.7 | 10
|
||||
| __XXH64__ | 64 | 19.4 GB/s | 71.0 | 10
|
||||
| SpookyHash | 64 | 19.3 GB/s | 53.2 | 10
|
||||
| Mum | 64 | 18.0 GB/s | 67.0 | 9 | Slightly worse [collisions]
|
||||
| __XXH32__ | 32 | 9.7 GB/s | 71.9 | 10
|
||||
| City32 | 32 | 9.1 GB/s | 66.0 | 10
|
||||
| Murmur3 | 32 | 3.9 GB/s | 56.1 | 10
|
||||
| SipHash | 64 | 3.0 GB/s | 43.2 | 10
|
||||
| FNV64 | 64 | 1.2 GB/s | 62.7 | 5 | Poor avalanche properties
|
||||
| Blake2 | 256 | 1.1 GB/s | 5.1 | 10 | Cryptographic
|
||||
| SHA1 | 160 | 0.8 GB/s | 5.6 | 10 | Cryptographic but broken
|
||||
| MD5 | 128 | 0.6 GB/s | 7.8 | 10 | Cryptographic but broken
|
||||
|
||||
[open source benchmark program]: https://github.com/Cyan4973/xxHash/tree/release/tests/bench
|
||||
[collisions]: https://github.com/Cyan4973/xxHash/wiki/Collision-ratio-comparison#collision-study
|
||||
|
||||
note 1: Small data velocity is a _rough_ evaluation of algorithm's efficiency on small data. For more detailed analysis, please refer to next paragraph.
|
||||
|
||||
note 2: some algorithms feature _faster than RAM_ speed. In which case, they can only reach their full speed potential when input is already in CPU cache (L3 or better). Otherwise, they max out on RAM speed limit.
|
||||
|
||||
### Small data
|
||||
|
||||
Performance on large data is only one part of the picture.
|
||||
Hashing is also very useful in constructions like hash tables and bloom filters.
|
||||
In these use cases, it's frequent to hash a lot of small data (starting at a few bytes).
|
||||
Algorithm's performance can be very different for such scenarios, since parts of the algorithm,
|
||||
such as initialization or finalization, become fixed cost.
|
||||
The impact of branch mis-prediction also becomes much more present.
|
||||
|
||||
XXH3 has been designed for excellent performance on both long and small inputs,
|
||||
which can be observed in the following graph:
|
||||
|
||||

|
||||
|
||||
For a more detailed analysis, please visit the wiki :
|
||||
https://github.com/Cyan4973/xxHash/wiki/Performance-comparison#benchmarks-concentrating-on-small-data-
|
||||
|
||||
Quality
|
||||
-------------------------
|
||||
|
||||
Speed is not the only property that matters.
|
||||
Produced hash values must respect excellent dispersion and randomness properties,
|
||||
so that any sub-section of it can be used to maximally spread out a table or index,
|
||||
as well as reduce the amount of collisions to the minimal theoretical level, following the [birthday paradox].
|
||||
|
||||
`xxHash` has been tested with Austin Appleby's excellent SMHasher test suite,
|
||||
and passes all tests, ensuring reasonable quality levels.
|
||||
It also passes extended tests from [newer forks of SMHasher], featuring additional scenarios and conditions.
|
||||
|
||||
Finally, xxHash provides its own [massive collision tester](https://github.com/Cyan4973/xxHash/tree/dev/tests/collisions),
|
||||
able to generate and compare billions of hashes to test the limits of 64-bit hash algorithms.
|
||||
On this front too, xxHash features good results, in line with the [birthday paradox].
|
||||
A more detailed analysis is documented [in the wiki](https://github.com/Cyan4973/xxHash/wiki/Collision-ratio-comparison).
|
||||
|
||||
[birthday paradox]: https://en.wikipedia.org/wiki/Birthday_problem
|
||||
[newer forks of SMHasher]: https://github.com/rurban/smhasher
|
||||
|
||||
|
||||
### Build modifiers
|
||||
|
||||
The following macros can be set at compilation time to modify libxxhash's behavior. They are generally disabled by default.
|
||||
|
||||
- `XXH_INLINE_ALL`: Make all functions `inline`, with implementations being directly included within `xxhash.h`.
|
||||
Inlining functions is beneficial for speed on small keys.
|
||||
It's _extremely effective_ when key length is expressed as _a compile time constant_,
|
||||
with performance improvements observed in the +200% range .
|
||||
See [this article](https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html) for details.
|
||||
- `XXH_PRIVATE_API`: same outcome as `XXH_INLINE_ALL`. Still available for legacy support.
|
||||
The name underlines that `XXH_*` symbol names will not be exported.
|
||||
- `XXH_NAMESPACE`: Prefixes all symbols with the value of `XXH_NAMESPACE`.
|
||||
This macro can only use compilable character set.
|
||||
Useful to evade symbol naming collisions,
|
||||
in case of multiple inclusions of xxHash's source code.
|
||||
Client applications still use the regular function names,
|
||||
as symbols are automatically translated through `xxhash.h`.
|
||||
- `XXH_FORCE_ALIGN_CHECK`: Use a faster direct read path when input is aligned.
|
||||
This option can result in dramatic performance improvement when input to hash is aligned on 32 or 64-bit boundaries,
|
||||
when running on architectures unable to load memory from unaligned addresses, or suffering a performance penalty from it.
|
||||
It is (slightly) detrimental on platform with good unaligned memory access performance (same instruction for both aligned and unaligned accesses).
|
||||
This option is automatically disabled on `x86`, `x64` and `aarch64`, and enabled on all other platforms.
|
||||
- `XXH_FORCE_MEMORY_ACCESS`: The default method `0` uses a portable `memcpy()` notation.
|
||||
Method `1` uses a gcc-specific `packed` attribute, which can provide better performance for some targets.
|
||||
Method `2` forces unaligned reads, which is not standard compliant, but might sometimes be the only way to extract better read performance.
|
||||
Method `3` uses a byteshift operation, which is best for old compilers which don't inline `memcpy()` or big-endian systems without a byteswap instruction.
|
||||
- `XXH_VECTOR` : manually select a vector instruction set (default: auto-selected at compilation time). Available instruction sets are `XXH_SCALAR`, `XXH_SSE2`, `XXH_AVX2`, `XXH_AVX512`, `XXH_NEON` and `XXH_VSX`. Compiler may require additional flags to ensure proper support (for example, `gcc` on linux will require `-mavx2` for `AVX2`, and `-mavx512f` for `AVX512`).
|
||||
- `XXH_NO_PREFETCH` : disable prefetching. Some platforms or situations may perform better without prefetching. XXH3 only.
|
||||
- `XXH_PREFETCH_DIST` : select prefetching distance. For close-to-metal adaptation to specific hardware platforms. XXH3 only.
|
||||
- `XXH_NO_STREAM`: Disables the streaming API, limiting it to single shot variants only.
|
||||
- `XXH_SIZE_OPT`: `0`: default, optimize for speed
|
||||
`1`: default for `-Os` and `-Oz`: disables some speed hacks for size optimization
|
||||
`2`: makes code as small as possible, performance may cry
|
||||
- `XXH_NO_INLINE_HINTS`: By default, xxHash uses `__attribute__((always_inline))` and `__forceinline` to improve performance at the cost of code size.
|
||||
Defining this macro to 1 will mark all internal functions as `static`, allowing the compiler to decide whether to inline a function or not.
|
||||
This is very useful when optimizing for smallest binary size,
|
||||
and is automatically defined when compiling with `-O0`, `-Os`, `-Oz`, or `-fno-inline` on GCC and Clang.
|
||||
This may also increase performance depending on compiler and architecture.
|
||||
- `XXH32_ENDJMP`: Switch multi-branch finalization stage of XXH32 by a single jump.
|
||||
This is generally undesirable for performance, especially when hashing inputs of random sizes.
|
||||
But depending on exact architecture and compiler, a jump might provide slightly better performance on small inputs. Disabled by default.
|
||||
- `XXH_NO_STDLIB`: Disable invocation of `<stdlib.h>` functions, notably `malloc()` and `free()`.
|
||||
`libxxhash`'s `XXH*_createState()` will always fail and return `NULL`.
|
||||
But one-shot hashing (like `XXH32()`) or streaming using statically allocated states
|
||||
still work as expected.
|
||||
This build flag is useful for embedded environments without dynamic allocation.
|
||||
- `XXH_STATIC_LINKING_ONLY`: gives access to internal state declaration, required for static allocation.
|
||||
Incompatible with dynamic linking, due to risks of ABI changes.
|
||||
- `XXH_NO_XXH3` : removes symbols related to `XXH3` (both 64 & 128 bits) from generated binary.
|
||||
Useful to reduce binary size, notably for applications which do not employ `XXH3`.
|
||||
- `XXH_NO_LONG_LONG`: removes compilation of algorithms relying on 64-bit types (`XXH3` and `XXH64`). Only `XXH32` will be compiled.
|
||||
Useful for targets (architectures and compilers) without 64-bit support.
|
||||
- `XXH_IMPORT`: MSVC specific: should only be defined for dynamic linking, as it prevents linkage errors.
|
||||
- `XXH_CPU_LITTLE_ENDIAN`: By default, endianness is determined by a runtime test resolved at compile time.
|
||||
If, for some reason, the compiler cannot simplify the runtime test, it can cost performance.
|
||||
It's possible to skip auto-detection and simply state that the architecture is little-endian by setting this macro to 1.
|
||||
Setting it to 0 states big-endian.
|
||||
- `XXH_DEBUGLEVEL` : When set to any value >= 1, enables `assert()` statements.
|
||||
This (slightly) slows down execution, but may help finding bugs during debugging sessions.
|
||||
|
||||
When compiling the Command Line Interface `xxhsum` using `make`, the following environment variables can also be set :
|
||||
- `DISPATCH=1` : use `xxh_x86dispatch.c`, to automatically select between `scalar`, `sse2`, `avx2` or `avx512` instruction set at runtime, depending on local host. This option is only valid for `x86`/`x64` systems.
|
||||
- `XXH_1ST_SPEED_TARGET` : select an initial speed target, expressed in MB/s, for the first speed test in benchmark mode. Benchmark will adjust the target at subsequent iterations, but the first test is made "blindly" by targeting this speed. Currently conservatively set to 10 MB/s, to support very slow (emulated) platforms.
|
||||
- `NODE_JS=1` : When compiling `xxhsum` for Node.js with Emscripten, this links the `NODERAWFS` library for unrestricted filesystem access and patches `isatty` to make the command line utility correctly detect the terminal. This does make the binary specific to Node.js.
|
||||
|
||||
### Building xxHash - Using vcpkg
|
||||
|
||||
You can download and install xxHash using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:
|
||||
|
||||
git clone https://github.com/Microsoft/vcpkg.git
|
||||
cd vcpkg
|
||||
./bootstrap-vcpkg.sh
|
||||
./vcpkg integrate install
|
||||
./vcpkg install xxhash
|
||||
|
||||
The xxHash port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
|
||||
|
||||
### Example
|
||||
|
||||
The simplest example calls xxhash 64-bit variant as a one-shot function
|
||||
generating a hash value from a single buffer, and invoked from a C/C++ program:
|
||||
|
||||
```C
|
||||
#include "xxhash.h"
|
||||
|
||||
(...)
|
||||
XXH64_hash_t hash = XXH64(buffer, size, seed);
|
||||
}
|
||||
```
|
||||
|
||||
Streaming variant is more involved, but makes it possible to provide data incrementally:
|
||||
|
||||
```C
|
||||
#include "stdlib.h" /* abort() */
|
||||
#include "xxhash.h"
|
||||
|
||||
|
||||
XXH64_hash_t calcul_hash_streaming(FileHandler fh)
|
||||
{
|
||||
/* create a hash state */
|
||||
XXH64_state_t* const state = XXH64_createState();
|
||||
if (state==NULL) abort();
|
||||
|
||||
size_t const bufferSize = SOME_SIZE;
|
||||
void* const buffer = malloc(bufferSize);
|
||||
if (buffer==NULL) abort();
|
||||
|
||||
/* Initialize state with selected seed */
|
||||
XXH64_hash_t const seed = 0; /* or any other value */
|
||||
if (XXH64_reset(state, seed) == XXH_ERROR) abort();
|
||||
|
||||
/* Feed the state with input data, any size, any number of times */
|
||||
(...)
|
||||
while ( /* some data left */ ) {
|
||||
size_t const length = get_more_data(buffer, bufferSize, fh);
|
||||
if (XXH64_update(state, buffer, length) == XXH_ERROR) abort();
|
||||
(...)
|
||||
}
|
||||
(...)
|
||||
|
||||
/* Produce the final hash value */
|
||||
XXH64_hash_t const hash = XXH64_digest(state);
|
||||
|
||||
/* State could be re-used; but in this example, it is simply freed */
|
||||
free(buffer);
|
||||
XXH64_freeState(state);
|
||||
|
||||
return hash;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### License
|
||||
|
||||
The library files `xxhash.c` and `xxhash.h` are BSD licensed.
|
||||
The utility `xxhsum` is GPL licensed.
|
||||
|
||||
|
||||
### Other programming languages
|
||||
|
||||
Beyond the C reference version,
|
||||
xxHash is also available from many different programming languages,
|
||||
thanks to great contributors.
|
||||
They are [listed here](http://www.xxhash.com/#other-languages).
|
||||
|
||||
|
||||
### Packaging status
|
||||
|
||||
Many distributions bundle a package manager
|
||||
which allows easy xxhash installation as both a `libxxhash` library
|
||||
and `xxhsum` command line interface.
|
||||
|
||||
[](https://repology.org/project/xxhash/versions)
|
||||
|
||||
|
||||
### Special Thanks
|
||||
|
||||
- Takayuki Matsuoka, aka @t-mat, for creating `xxhsum -c` and great support during early xxh releases
|
||||
- Mathias Westerdahl, aka @JCash, for introducing the first version of `XXH64`
|
||||
- Devin Hussey, aka @easyaspi314, for incredible low-level optimizations on `XXH3` and `XXH128`
|
||||
@@ -1,13 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security updates are applied only to the latest release.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.
|
||||
|
||||
Please disclose it at [security advisory](https://github.com/Cyan4973/xxHash/security/advisories/new).
|
||||
|
||||
This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure.
|
||||
@@ -1,9 +0,0 @@
|
||||
xxHash Specification
|
||||
=======================
|
||||
|
||||
This directory contains material defining the xxHash algorithm.
|
||||
It's described in [this specification document](xxhash_spec.md).
|
||||
|
||||
The algorithm is also be illustrated by a [simple educational library](https://github.com/easyaspi314/xxhash-clean),
|
||||
written by @easyaspi314 and designed for readability
|
||||
(as opposed to the reference library which is designed for speed).
|
||||
@@ -1,206 +0,0 @@
|
||||
module xxhash where
|
||||
|
||||
/**
|
||||
* The 32-bit variant of xxHash. The first argument is the sequence
|
||||
* of L bytes to hash. The second argument is a seed value.
|
||||
*/
|
||||
XXH32 : {L} (fin L) => [L][8] -> [32] -> [32]
|
||||
XXH32 input seed = XXH32_avalanche acc1
|
||||
where (stripes16 # stripes4 # stripes1) = input
|
||||
accR = foldl XXH32_rounds (XXH32_init seed) (split stripes16 : [L/16][16][8])
|
||||
accL = `(L % 2^^32) + if (`L:Integer) < 16
|
||||
then seed + PRIME32_5
|
||||
else XXH32_converge accR
|
||||
acc4 = foldl XXH32_digest4 accL (split stripes4 : [(L%16)/4][4][8])
|
||||
acc1 = foldl XXH32_digest1 acc4 (stripes1 : [L%4][8])
|
||||
|
||||
/**
|
||||
* The 64-bit variant of xxHash. The first argument is the sequence
|
||||
* of L bytes to hash. The second argument is a seed value.
|
||||
*/
|
||||
XXH64 : {L} (fin L) => [L][8] -> [64] -> [64]
|
||||
XXH64 input seed = XXH64_avalanche acc1
|
||||
where (stripes32 # stripes8 # stripes4 # stripes1) = input
|
||||
accR = foldl XXH64_rounds (XXH64_init seed) (split stripes32 : [L/32][32][8])
|
||||
accL = `(L % 2^^64) + if (`L:Integer) < 32
|
||||
then seed + PRIME64_5
|
||||
else XXH64_converge accR
|
||||
acc8 = foldl XXH64_digest8 accL (split stripes8 : [(L%32)/8][8][8])
|
||||
acc4 = foldl XXH64_digest4 acc8 (split stripes4 : [(L%8)/4][4][8])
|
||||
acc1 = foldl XXH64_digest1 acc4 (stripes1 : [L%4][8])
|
||||
|
||||
private
|
||||
|
||||
//Utility functions
|
||||
|
||||
/**
|
||||
* Combines a sequence of bytes into a word using the little-endian
|
||||
* convention.
|
||||
*/
|
||||
toLE bytes = join (reverse bytes)
|
||||
|
||||
//32-bit xxHash helper functions
|
||||
|
||||
//32-bit prime number constants
|
||||
PRIME32_1 = 0x9E3779B1 : [32]
|
||||
PRIME32_2 = 0x85EBCA77 : [32]
|
||||
PRIME32_3 = 0xC2B2AE3D : [32]
|
||||
PRIME32_4 = 0x27D4EB2F : [32]
|
||||
PRIME32_5 = 0x165667B1 : [32]
|
||||
|
||||
/**
|
||||
* The property shows that the hexadecimal representation of the
|
||||
* PRIME32 constants is the same as the binary representation.
|
||||
*/
|
||||
property PRIME32s_as_bits_correct =
|
||||
(PRIME32_1 == 0b10011110001101110111100110110001) /\
|
||||
(PRIME32_2 == 0b10000101111010111100101001110111) /\
|
||||
(PRIME32_3 == 0b11000010101100101010111000111101) /\
|
||||
(PRIME32_4 == 0b00100111110101001110101100101111) /\
|
||||
(PRIME32_5 == 0b00010110010101100110011110110001)
|
||||
|
||||
/**
|
||||
* This function initializes the four internal accumulators of XXH32.
|
||||
*/
|
||||
XXH32_init : [32] -> [4][32]
|
||||
XXH32_init seed = [acc1, acc2, acc3, acc4]
|
||||
where acc1 = seed + PRIME32_1 + PRIME32_2
|
||||
acc2 = seed + PRIME32_2
|
||||
acc3 = seed + 0
|
||||
acc4 = seed - PRIME32_1
|
||||
|
||||
/**
|
||||
* This processes a single lane of the main round function of XXH32.
|
||||
*/
|
||||
XXH32_round : [32] -> [32] -> [32]
|
||||
XXH32_round accN laneN = ((accN + laneN * PRIME32_2) <<< 13) * PRIME32_1
|
||||
|
||||
/**
|
||||
* This is the main round function of XXH32 and processes a stripe,
|
||||
* i.e. 4 lanes with 4 bytes each.
|
||||
*/
|
||||
XXH32_rounds : [4][32] -> [16][8] -> [4][32]
|
||||
XXH32_rounds accs stripe =
|
||||
[ XXH32_round accN (toLE laneN) | accN <- accs | laneN <- split stripe ]
|
||||
|
||||
/**
|
||||
* This function combines the four lane accumulators into a single
|
||||
* 32-bit value.
|
||||
*/
|
||||
XXH32_converge : [4][32] -> [32]
|
||||
XXH32_converge [acc1, acc2, acc3, acc4] =
|
||||
(acc1 <<< 1) + (acc2 <<< 7) + (acc3 <<< 12) + (acc4 <<< 18)
|
||||
|
||||
/**
|
||||
* This function digests a four byte lane
|
||||
*/
|
||||
XXH32_digest4 : [32] -> [4][8] -> [32]
|
||||
XXH32_digest4 acc lane = ((acc + toLE lane * PRIME32_3) <<< 17) * PRIME32_4
|
||||
|
||||
/**
|
||||
* This function digests a single byte lane
|
||||
*/
|
||||
XXH32_digest1 : [32] -> [8] -> [32]
|
||||
XXH32_digest1 acc lane = ((acc + (0 # lane) * PRIME32_5) <<< 11) * PRIME32_1
|
||||
|
||||
/**
|
||||
* This function ensures that all input bits have a chance to impact
|
||||
* any bit in the output digest, resulting in an unbiased
|
||||
* distribution.
|
||||
*/
|
||||
XXH32_avalanche : [32] -> [32]
|
||||
XXH32_avalanche acc0 = acc5
|
||||
where acc1 = acc0 ^ (acc0 >> 15)
|
||||
acc2 = acc1 * PRIME32_2
|
||||
acc3 = acc2 ^ (acc2 >> 13)
|
||||
acc4 = acc3 * PRIME32_3
|
||||
acc5 = acc4 ^ (acc4 >> 16)
|
||||
|
||||
//64-bit xxHash helper functions
|
||||
|
||||
//64-bit prime number constants
|
||||
PRIME64_1 = 0x9E3779B185EBCA87 : [64]
|
||||
PRIME64_2 = 0xC2B2AE3D27D4EB4F : [64]
|
||||
PRIME64_3 = 0x165667B19E3779F9 : [64]
|
||||
PRIME64_4 = 0x85EBCA77C2B2AE63 : [64]
|
||||
PRIME64_5 = 0x27D4EB2F165667C5 : [64]
|
||||
|
||||
/**
|
||||
* The property shows that the hexadecimal representation of the
|
||||
* PRIME64 constants is the same as the binary representation.
|
||||
*/
|
||||
property PRIME64s_as_bits_correct =
|
||||
(PRIME64_1 == 0b1001111000110111011110011011000110000101111010111100101010000111) /\
|
||||
(PRIME64_2 == 0b1100001010110010101011100011110100100111110101001110101101001111) /\
|
||||
(PRIME64_3 == 0b0001011001010110011001111011000110011110001101110111100111111001) /\
|
||||
(PRIME64_4 == 0b1000010111101011110010100111011111000010101100101010111001100011) /\
|
||||
(PRIME64_5 == 0b0010011111010100111010110010111100010110010101100110011111000101)
|
||||
|
||||
/**
|
||||
* This function initializes the four internal accumulators of XXH64.
|
||||
*/
|
||||
XXH64_init : [64] -> [4][64]
|
||||
XXH64_init seed = [acc1, acc2, acc3, acc4]
|
||||
where acc1 = seed + PRIME64_1 + PRIME64_2
|
||||
acc2 = seed + PRIME64_2
|
||||
acc3 = seed + 0
|
||||
acc4 = seed - PRIME64_1
|
||||
|
||||
/**
|
||||
* This processes a single lane of the main round function of XXH64.
|
||||
*/
|
||||
XXH64_round : [64] -> [64] -> [64]
|
||||
XXH64_round accN laneN = ((accN + laneN * PRIME64_2) <<< 31) * PRIME64_1
|
||||
|
||||
/**
|
||||
* This is the main round function of XXH64 and processes a stripe,
|
||||
* i.e. 4 lanes with 8 bytes each.
|
||||
*/
|
||||
XXH64_rounds : [4][64] -> [32][8] -> [4][64]
|
||||
XXH64_rounds accs stripe =
|
||||
[ XXH64_round accN (toLE laneN) | accN <- accs | laneN <- split stripe ]
|
||||
|
||||
/**
|
||||
* This is a helper function, used to merge the four lane accumulators.
|
||||
*/
|
||||
mergeAccumulator : [64] -> [64] -> [64]
|
||||
mergeAccumulator acc accN = (acc ^ XXH64_round 0 accN) * PRIME64_1 + PRIME64_4
|
||||
|
||||
/**
|
||||
* This function combines the four lane accumulators into a single
|
||||
* 64-bit value.
|
||||
*/
|
||||
XXH64_converge : [4][64] -> [64]
|
||||
XXH64_converge [acc1, acc2, acc3, acc4] =
|
||||
foldl mergeAccumulator ((acc1 <<< 1) + (acc2 <<< 7) + (acc3 <<< 12) + (acc4 <<< 18)) [acc1, acc2, acc3, acc4]
|
||||
|
||||
/**
|
||||
* This function digests an eight byte lane
|
||||
*/
|
||||
XXH64_digest8 : [64] -> [8][8] -> [64]
|
||||
XXH64_digest8 acc lane = ((acc ^ XXH64_round 0 (toLE lane)) <<< 27) * PRIME64_1 + PRIME64_4
|
||||
|
||||
/**
|
||||
* This function digests a four byte lane
|
||||
*/
|
||||
XXH64_digest4 : [64] -> [4][8] -> [64]
|
||||
XXH64_digest4 acc lane = ((acc ^ (0 # toLE lane) * PRIME64_1) <<< 23) * PRIME64_2 + PRIME64_3
|
||||
|
||||
/**
|
||||
* This function digests a single byte lane
|
||||
*/
|
||||
XXH64_digest1 : [64] -> [8] -> [64]
|
||||
XXH64_digest1 acc lane = ((acc ^ (0 # lane) * PRIME64_5) <<< 11) * PRIME64_1
|
||||
|
||||
/**
|
||||
* This function ensures that all input bits have a chance to impact
|
||||
* any bit in the output digest, resulting in an unbiased
|
||||
* distribution.
|
||||
*/
|
||||
XXH64_avalanche : [64] -> [64]
|
||||
XXH64_avalanche acc0 = acc5
|
||||
where acc1 = acc0 ^ (acc0 >> 33)
|
||||
acc2 = acc1 * PRIME64_2
|
||||
acc3 = acc2 ^ (acc2 >> 29)
|
||||
acc4 = acc3 * PRIME64_3
|
||||
acc5 = acc4 ^ (acc4 >> 32)
|
||||
@@ -1,820 +0,0 @@
|
||||
xxHash fast digest algorithm
|
||||
======================
|
||||
|
||||
### Notices
|
||||
|
||||
Copyright (c) Yann Collet
|
||||
|
||||
Permission is granted to copy and distribute this document
|
||||
for any purpose and without charge,
|
||||
including translations into other languages
|
||||
and incorporation into compilations,
|
||||
provided that the copyright notice and this notice are preserved,
|
||||
and that any substantive changes or deletions from the original
|
||||
are clearly marked.
|
||||
Distribution of this document is unlimited.
|
||||
|
||||
### Version
|
||||
|
||||
0.2.0 (29/06/23)
|
||||
|
||||
|
||||
Table of Contents
|
||||
---------------------
|
||||
- [Introduction](#introduction)
|
||||
- [XXH32 algorithm description](#xxh32-algorithm-description)
|
||||
- [XXH64 algorithm description](#xxh64-algorithm-description)
|
||||
- [XXH3 algorithm description](#xxh3-algorithm-overview)
|
||||
- [Small inputs](#xxh3-algorithm-description-for-small-inputs)
|
||||
- [Medium inputs](#xxh3-algorithm-description-for-medium-inputs)
|
||||
- [Large inputs](#xxh3-algorithm-description-for-large-inputs)
|
||||
- [Performance considerations](#performance-considerations)
|
||||
- [Reference Implementation](#reference-implementation)
|
||||
|
||||
|
||||
Introduction
|
||||
----------------
|
||||
|
||||
This document describes the xxHash digest algorithm for both 32-bit and 64-bit variants, named `XXH32` and `XXH64`. The algorithm takes an input a message of arbitrary length and an optional seed value, then produces an output of 32 or 64-bit as "fingerprint" or "digest".
|
||||
|
||||
xxHash is primarily designed for speed. It is labeled non-cryptographic, and is not meant to avoid intentional collisions (same digest for 2 different messages), or to prevent producing a message with a predefined digest.
|
||||
|
||||
XXH32 is designed to be fast on 32-bit machines.
|
||||
XXH64 is designed to be fast on 64-bit machines.
|
||||
Both variants produce different output.
|
||||
However, a given variant shall produce exactly the same output, irrespective of the cpu / os used. In particular, the result remains identical whatever the endianness and width of the cpu is.
|
||||
|
||||
### Operation notations
|
||||
|
||||
All operations are performed modulo {32,64} bits. Arithmetic overflows are expected.
|
||||
`XXH32` uses 32-bit modular operations.
|
||||
`XXH64` and `XXH3` use 64-bit modular operations.
|
||||
When an operation ingests input or secret as multi-bytes values, it reads it using little-endian convention.
|
||||
|
||||
- `+`: denotes modular addition
|
||||
- `-`: denotes modular subtraction
|
||||
- `*`: denotes modular multiplication
|
||||
- **Exception:** In `XXH3`, if it is in the form `(u128)x * (u128)y`, it denotes 64-bit by 64-bit normal multiplication into a full 128-bit result.
|
||||
- `X <<< s`: denotes the value obtained by circularly shifting (rotating) `X` left by `s` bit positions.
|
||||
- `X >> s`: denotes the value obtained by shifting `X` right by s bit positions. Upper `s` bits become `0`.
|
||||
- `X << s`: denotes the value obtained by shifting `X` left by s bit positions. Lower `s` bits become `0`.
|
||||
- `X xor Y`: denotes the bit-wise XOR of `X` and `Y` (same width).
|
||||
- `X | Y`: denotes the bit-wise OR of `X` and `Y` (same width).
|
||||
- `~X`: denotes the bit-wise negation of `X`.
|
||||
|
||||
|
||||
XXH32 Algorithm Description
|
||||
-------------------------------------
|
||||
|
||||
### Overview
|
||||
|
||||
We begin by supposing that we have a message of any length `L` as input, and that we wish to find its digest. Here `L` is an arbitrary nonnegative integer; `L` may be zero. The following steps are performed to compute the digest of the message.
|
||||
|
||||
The algorithm collect and transform input in _stripes_ of 16 bytes. The transforms are stored inside 4 "accumulators", each one storing an unsigned 32-bit value. Each accumulator can be processed independently in parallel, speeding up processing for cpu with multiple execution units.
|
||||
|
||||
The algorithm uses 32-bits addition, multiplication, rotate, shift and xor operations. Many operations require some 32-bits prime number constants, all defined below:
|
||||
|
||||
```c
|
||||
static const u32 PRIME32_1 = 0x9E3779B1U; // 0b10011110001101110111100110110001
|
||||
static const u32 PRIME32_2 = 0x85EBCA77U; // 0b10000101111010111100101001110111
|
||||
static const u32 PRIME32_3 = 0xC2B2AE3DU; // 0b11000010101100101010111000111101
|
||||
static const u32 PRIME32_4 = 0x27D4EB2FU; // 0b00100111110101001110101100101111
|
||||
static const u32 PRIME32_5 = 0x165667B1U; // 0b00010110010101100110011110110001
|
||||
```
|
||||
|
||||
These constants are prime numbers, and feature a good mix of bits 1 and 0, neither too regular, nor too dissymmetric. These properties help dispersion capabilities.
|
||||
|
||||
### Step 1. Initialize internal accumulators
|
||||
|
||||
Each accumulator gets an initial value based on optional `seed` input. Since the `seed` is optional, it can be `0`.
|
||||
|
||||
```c
|
||||
u32 acc1 = seed + PRIME32_1 + PRIME32_2;
|
||||
u32 acc2 = seed + PRIME32_2;
|
||||
u32 acc3 = seed + 0;
|
||||
u32 acc4 = seed - PRIME32_1;
|
||||
```
|
||||
|
||||
#### Special case: input is less than 16 bytes
|
||||
|
||||
When the input is too small (< 16 bytes), the algorithm will not process any stripes. Consequently, it will not make use of parallel accumulators.
|
||||
|
||||
In this case, a simplified initialization is performed, using a single accumulator:
|
||||
|
||||
```c
|
||||
u32 acc = seed + PRIME32_5;
|
||||
```
|
||||
|
||||
The algorithm then proceeds directly to step 4.
|
||||
|
||||
### Step 2. Process stripes
|
||||
|
||||
A stripe is a contiguous segment of 16 bytes.
|
||||
It is evenly divided into 4 _lanes_, of 4 bytes each.
|
||||
The first lane is used to update accumulator 1, the second lane is used to update accumulator 2, and so on.
|
||||
|
||||
Each lane read its associated 32-bit value using __little-endian__ convention.
|
||||
|
||||
For each {lane, accumulator}, the update process is called a _round_, and applies the following formula:
|
||||
|
||||
```c
|
||||
accN = accN + (laneN * PRIME32_2);
|
||||
accN = accN <<< 13;
|
||||
accN = accN * PRIME32_1;
|
||||
```
|
||||
|
||||
This shuffles the bits so that any bit from input _lane_ impacts several bits in output _accumulator_. All operations are performed modulo 2^32.
|
||||
|
||||
Input is consumed one full stripe at a time. Step 2 is looped as many times as necessary to consume the whole input, except for the last remaining bytes which cannot form a stripe (< 16 bytes).
|
||||
When that happens, move to step 3.
|
||||
|
||||
### Step 3. Accumulator convergence
|
||||
|
||||
All 4 lane accumulators from the previous steps are merged to produce a single remaining accumulator of the same width (32-bit). The associated formula is as follows:
|
||||
|
||||
```c
|
||||
acc = (acc1 <<< 1) + (acc2 <<< 7) + (acc3 <<< 12) + (acc4 <<< 18);
|
||||
```
|
||||
|
||||
### Step 4. Add input length
|
||||
|
||||
The input total length is presumed known at this stage. This step is just about adding the length to accumulator, so that it participates to final mixing.
|
||||
|
||||
```c
|
||||
acc = acc + (u32)inputLength;
|
||||
```
|
||||
|
||||
Note that, if input length is so large that it requires more than 32-bits, only the lower 32-bits are added to the accumulator.
|
||||
|
||||
### Step 5. Consume remaining input
|
||||
|
||||
There may be up to 15 bytes remaining to consume from the input.
|
||||
The final stage will digest them according to following pseudo-code:
|
||||
|
||||
```c
|
||||
while (remainingLength >= 4) {
|
||||
lane = read_32bit_little_endian(input_ptr);
|
||||
acc = acc + lane * PRIME32_3;
|
||||
acc = (acc <<< 17) * PRIME32_4;
|
||||
input_ptr += 4; remainingLength -= 4;
|
||||
}
|
||||
|
||||
while (remainingLength >= 1) {
|
||||
lane = read_byte(input_ptr);
|
||||
acc = acc + lane * PRIME32_5;
|
||||
acc = (acc <<< 11) * PRIME32_1;
|
||||
input_ptr += 1; remainingLength -= 1;
|
||||
}
|
||||
```
|
||||
|
||||
This process ensures that all input bytes are present in the final mix.
|
||||
|
||||
### Step 6. Final mix (avalanche)
|
||||
|
||||
The final mix ensures that all input bits have a chance to impact any bit in the output digest, resulting in an unbiased distribution. This is also called avalanche effect.
|
||||
|
||||
```c
|
||||
acc = acc xor (acc >> 15);
|
||||
acc = acc * PRIME32_2;
|
||||
acc = acc xor (acc >> 13);
|
||||
acc = acc * PRIME32_3;
|
||||
acc = acc xor (acc >> 16);
|
||||
```
|
||||
|
||||
### Step 7. Output
|
||||
|
||||
The `XXH32()` function produces an unsigned 32-bit value as output.
|
||||
|
||||
For systems which require to store and/or display the result in binary or hexadecimal format, the canonical format is defined to reproduce the same value as the natural decimal format, hence follows __big-endian__ convention (most significant byte first).
|
||||
|
||||
|
||||
XXH64 Algorithm Description
|
||||
-------------------------------------
|
||||
|
||||
### Overview
|
||||
|
||||
`XXH64`'s algorithm structure is very similar to `XXH32` one. The major difference is that `XXH64` uses 64-bit arithmetic, speeding up memory transfer for 64-bit compliant systems, but also relying on cpu capability to efficiently perform 64-bit operations.
|
||||
|
||||
The algorithm collects and transforms input in _stripes_ of 32 bytes. The transforms are stored inside 4 "accumulators", each one storing an unsigned 64-bit value. Each accumulator can be processed independently in parallel, speeding up processing for cpu with multiple execution units.
|
||||
|
||||
The algorithm uses 64-bit addition, multiplication, rotate, shift and xor operations. Many operations require some 64-bit prime number constants, all defined below:
|
||||
|
||||
```c
|
||||
static const u64 PRIME64_1 = 0x9E3779B185EBCA87ULL; // 0b1001111000110111011110011011000110000101111010111100101010000111
|
||||
static const u64 PRIME64_2 = 0xC2B2AE3D27D4EB4FULL; // 0b1100001010110010101011100011110100100111110101001110101101001111
|
||||
static const u64 PRIME64_3 = 0x165667B19E3779F9ULL; // 0b0001011001010110011001111011000110011110001101110111100111111001
|
||||
static const u64 PRIME64_4 = 0x85EBCA77C2B2AE63ULL; // 0b1000010111101011110010100111011111000010101100101010111001100011
|
||||
static const u64 PRIME64_5 = 0x27D4EB2F165667C5ULL; // 0b0010011111010100111010110010111100010110010101100110011111000101
|
||||
```
|
||||
|
||||
These constants are prime numbers, and feature a good mix of bits 1 and 0, neither too regular, nor too dissymmetric. These properties help dispersion capabilities.
|
||||
|
||||
### Step 1. Initialize internal accumulators
|
||||
|
||||
Each accumulator gets an initial value based on optional `seed` input. Since the `seed` is optional, it can be `0`.
|
||||
|
||||
```c
|
||||
u64 acc1 = seed + PRIME64_1 + PRIME64_2;
|
||||
u64 acc2 = seed + PRIME64_2;
|
||||
u64 acc3 = seed + 0;
|
||||
u64 acc4 = seed - PRIME64_1;
|
||||
```
|
||||
|
||||
#### Special case: input is less than 32 bytes
|
||||
|
||||
When the input is too small (< 32 bytes), the algorithm will not process any stripes. Consequently, it will not make use of parallel accumulators.
|
||||
|
||||
In this case, a simplified initialization is performed, using a single accumulator:
|
||||
|
||||
```c
|
||||
u64 acc = seed + PRIME64_5;
|
||||
```
|
||||
|
||||
The algorithm then proceeds directly to step 4.
|
||||
|
||||
### Step 2. Process stripes
|
||||
|
||||
A stripe is a contiguous segment of 32 bytes.
|
||||
It is evenly divided into 4 _lanes_, of 8 bytes each.
|
||||
The first lane is used to update accumulator 1, the second lane is used to update accumulator 2, and so on.
|
||||
|
||||
Each lane read its associated 64-bit value using __little-endian__ convention.
|
||||
|
||||
For each {lane, accumulator}, the update process is called a _round_, and applies the following formula:
|
||||
|
||||
```c
|
||||
round(accN,laneN):
|
||||
accN = accN + (laneN * PRIME64_2);
|
||||
accN = accN <<< 31;
|
||||
return accN * PRIME64_1;
|
||||
```
|
||||
|
||||
This shuffles the bits so that any bit from input _lane_ impacts several bits in output _accumulator_. All operations are performed modulo 2^64.
|
||||
|
||||
Input is consumed one full stripe at a time. Step 2 is looped as many times as necessary to consume the whole input, except for the last remaining bytes which cannot form a stripe (< 32 bytes).
|
||||
When that happens, move to step 3.
|
||||
|
||||
### Step 3. Accumulator convergence
|
||||
|
||||
All 4 lane accumulators from previous steps are merged to produce a single remaining accumulator of same width (64-bit). The associated formula is as follows.
|
||||
|
||||
Note that accumulator convergence is more complex than 32-bit variant, and requires to define another function called _mergeAccumulator()_:
|
||||
|
||||
```c
|
||||
mergeAccumulator(acc,accN):
|
||||
acc = acc xor round(0, accN);
|
||||
acc = acc * PRIME64_1;
|
||||
return acc + PRIME64_4;
|
||||
```
|
||||
|
||||
which is then used in the convergence formula:
|
||||
|
||||
```c
|
||||
acc = (acc1 <<< 1) + (acc2 <<< 7) + (acc3 <<< 12) + (acc4 <<< 18);
|
||||
acc = mergeAccumulator(acc, acc1);
|
||||
acc = mergeAccumulator(acc, acc2);
|
||||
acc = mergeAccumulator(acc, acc3);
|
||||
acc = mergeAccumulator(acc, acc4);
|
||||
```
|
||||
|
||||
### Step 4. Add input length
|
||||
|
||||
The input total length is presumed known at this stage. This step is just about adding the length to accumulator, so that it participates to final mixing.
|
||||
|
||||
```c
|
||||
acc = acc + inputLength;
|
||||
```
|
||||
|
||||
### Step 5. Consume remaining input
|
||||
|
||||
There may be up to 31 bytes remaining to consume from the input.
|
||||
The final stage will digest them according to following pseudo-code:
|
||||
|
||||
```c
|
||||
while (remainingLength >= 8) {
|
||||
lane = read_64bit_little_endian(input_ptr);
|
||||
acc = acc xor round(0, lane);
|
||||
acc = (acc <<< 27) * PRIME64_1;
|
||||
acc = acc + PRIME64_4;
|
||||
input_ptr += 8; remainingLength -= 8;
|
||||
}
|
||||
|
||||
if (remainingLength >= 4) {
|
||||
lane = read_32bit_little_endian(input_ptr);
|
||||
acc = acc xor (lane * PRIME64_1);
|
||||
acc = (acc <<< 23) * PRIME64_2;
|
||||
acc = acc + PRIME64_3;
|
||||
input_ptr += 4; remainingLength -= 4;
|
||||
}
|
||||
|
||||
while (remainingLength >= 1) {
|
||||
lane = read_byte(input_ptr);
|
||||
acc = acc xor (lane * PRIME64_5);
|
||||
acc = (acc <<< 11) * PRIME64_1;
|
||||
input_ptr += 1; remainingLength -= 1;
|
||||
}
|
||||
```
|
||||
|
||||
This process ensures that all input bytes are present in the final mix.
|
||||
|
||||
### Step 6. Final mix (avalanche)
|
||||
|
||||
The final mix ensures that all input bits have a chance to impact any bit in the output digest, resulting in an unbiased distribution. This is also called avalanche effect.
|
||||
|
||||
```c
|
||||
acc = acc xor (acc >> 33);
|
||||
acc = acc * PRIME64_2;
|
||||
acc = acc xor (acc >> 29);
|
||||
acc = acc * PRIME64_3;
|
||||
acc = acc xor (acc >> 32);
|
||||
```
|
||||
|
||||
### Step 7. Output
|
||||
|
||||
The `XXH64()` function produces an unsigned 64-bit value as output.
|
||||
|
||||
For systems which require to store and/or display the result in binary or hexadecimal format, the canonical format is defined to reproduce the same value as the natural decimal format, hence follows __big-endian__ convention (most significant byte first).
|
||||
|
||||
XXH3 Algorithm Overview
|
||||
-------------------------------------
|
||||
|
||||
XXH3 comes in two different versions: XXH3-64 and XXH3-128 (or XXH128), producing 64 and 128 bits of output, respectively.
|
||||
|
||||
XXH3 uses different algorithms for small (0-16 bytes), medium (17-240 bytes), and large (241+ bytes) inputs. The algorithms for small and medium inputs are optimized for performance. The three algorithms are described in the following sections.
|
||||
|
||||
Many operations require some 64-bit prime number constants, which are mostly the same constants used in XXH32 and XXH64, all defined below:
|
||||
|
||||
```c
|
||||
static const u64 PRIME32_1 = 0x9E3779B1U; // 0b10011110001101110111100110110001
|
||||
static const u64 PRIME32_2 = 0x85EBCA77U; // 0b10000101111010111100101001110111
|
||||
static const u64 PRIME32_3 = 0xC2B2AE3DU; // 0b11000010101100101010111000111101
|
||||
static const u64 PRIME64_1 = 0x9E3779B185EBCA87ULL; // 0b1001111000110111011110011011000110000101111010111100101010000111
|
||||
static const u64 PRIME64_2 = 0xC2B2AE3D27D4EB4FULL; // 0b1100001010110010101011100011110100100111110101001110101101001111
|
||||
static const u64 PRIME64_3 = 0x165667B19E3779F9ULL; // 0b0001011001010110011001111011000110011110001101110111100111111001
|
||||
static const u64 PRIME64_4 = 0x85EBCA77C2B2AE63ULL; // 0b1000010111101011110010100111011111000010101100101010111001100011
|
||||
static const u64 PRIME64_5 = 0x27D4EB2F165667C5ULL; // 0b0010011111010100111010110010111100010110010101100110011111000101
|
||||
static const u64 PRIME_MX1 = 0x165667919E3779F9ULL; // 0b0001011001010110011001111001000110011110001101110111100111111001
|
||||
static const u64 PRIME_MX2 = 0x9FB21C651E98DF25ULL; // 0b1001111110110010000111000110010100011110100110001101111100100101
|
||||
```
|
||||
|
||||
The `XXH3_64bits()` function produces an unsigned 64-bit value.
|
||||
The `XXH3_128bits()` function produces a `XXH128_hash_t` struct containing `low64` and `high64` - the lower and higher 64-bit half values of the result, respectively.
|
||||
|
||||
For systems requiring storing and/or displaying the result in binary or hexadecimal format, the canonical format is defined to reproduce the same value as the natural decimal format, hence following **big-endian** convention (most significant byte first).
|
||||
|
||||
### Seed and Secret
|
||||
|
||||
XXH3 provides seeded hashing by introducing two configurable constants used in the hashing process: the seed and the secret. The seed is an unsigned 64-bit value, and the secret is an array of bytes that is at least 136 bytes in size. The default seed is 0, and the default secret is the following 192-byte value:
|
||||
|
||||
```c
|
||||
static const u8 defaultSecret[192] = {
|
||||
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
|
||||
0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
|
||||
0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
|
||||
0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
|
||||
0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
|
||||
0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
|
||||
0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
|
||||
0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
|
||||
0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
|
||||
0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
|
||||
0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
|
||||
0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
|
||||
};
|
||||
```
|
||||
|
||||
The seed and the secret can be optionally specified using the `*_withSecret` and `*_withSeed` versions of the hash function.
|
||||
|
||||
The seed and the secret cannot be specified simultaneously (`*_withSecretAndSeed` is actually `*_withSeed` for short and medium inputs <= 240 bytes, and `*_withSecret` for large inputs). When one is specified, the other one uses the default value.
|
||||
There is one exception though: when input is large (> 240 bytes) and a seed is given, a secret is derived from the seed value and the default secret using the following procedure:
|
||||
|
||||
```c
|
||||
deriveSecret(u64 seed):
|
||||
u64 derivedSecret[24] = defaultSecret[0:192];
|
||||
for (i = 0; i < 12; i++) {
|
||||
derivedSecret[i*2] += seed;
|
||||
derivedSecret[i*2+1] -= seed;
|
||||
}
|
||||
return derivedSecret; // convert to u8[192] (little-endian)
|
||||
```
|
||||
|
||||
The derivation treats the secrets as 24 64-bit values. In XXH3 algorithms, the secret is always read similarly by treating a contiguous segment of the array as one or more 32-bit or 64-bit values. **The secret values are always read using little-endian convention**.
|
||||
|
||||
### Final Mixing Step (avalanche)
|
||||
|
||||
To make sure that all input bits have a chance to impact any bit in the output digest (avalanche effect), the final step of the XXH3 algorithm is usually one of the two fixed operations that mix the bits in a 64-bit value. These operations are denoted `avalanche()` and `avalanche_XXH64()` in the following XXH3 description.
|
||||
|
||||
```c
|
||||
avalanche(u64 x):
|
||||
x = x xor (x >> 37);
|
||||
x = x * PRIME_MX1;
|
||||
x = x xor (x >> 32);
|
||||
return x;
|
||||
|
||||
avalanche_XXH64(u64 x):
|
||||
x = x xor (x >> 33);
|
||||
x = x * PRIME64_2;
|
||||
x = x xor (x >> 29);
|
||||
x = x * PRIME64_3;
|
||||
x = x xor (x >> 32);
|
||||
return x;
|
||||
```
|
||||
|
||||
XXH3 Algorithm Description (for small inputs)
|
||||
-------------------------------------
|
||||
|
||||
The algorithm for small inputs (0-16 bytes of input) is further divided into 4 cases: empty, 1-3 bytes, 4-8 bytes, and 9-16 bytes of input.
|
||||
|
||||
The algorithm uses byte-swap operations. The byte-swap operation reverses the byte order in a 32-bit or 64-bit value. It is denoted `bswap32` and `bswap64` for its 32-bit and 64-bit versions, respectively.
|
||||
|
||||
### Empty input
|
||||
|
||||
The hash of empty input is calculated from the seed and a segment of the secret:
|
||||
|
||||
```c
|
||||
XXH3_64_empty():
|
||||
u64 secretWords[2] = secret[56:72];
|
||||
return avalanche_XXH64(seed xor secretWords[0] xor secretWords[1]);
|
||||
|
||||
XXH3_128_empty():
|
||||
u64 secretWords[4] = secret[64:96];
|
||||
return {avalanche_XXH64(seed xor secretWords[0] xor secretWords[1]), // lower half
|
||||
avalanche_XXH64(seed xor secretWords[2] xor secretWords[3])}; // higher half
|
||||
```
|
||||
|
||||
### 1-3 bytes of input
|
||||
|
||||
The algorithm starts from a single 32-bit value combining the input bytes and its length:
|
||||
|
||||
```c
|
||||
u32 combined = (u32)input[inputLength-1] | ((u32)inputLength << 8) |
|
||||
((u32)input[0] << 16) | ((u32)input[inputLength>>1] << 24);
|
||||
// LSB 8 16 24 MSB
|
||||
// | last byte | length | first byte | middle-or-last byte |
|
||||
```
|
||||
|
||||
Then the final output is calculated from the value and the first 8 bytes (XXH3-64) or 16 bytes (XXH3-128) of the secret to produce the final result. The secret here is read as 32-bit values instead of the usual 64-bit values.
|
||||
|
||||
```c
|
||||
XXH3_64_1to3():
|
||||
u32 secretWords[2] = secret[0:8];
|
||||
u64 value = ((u64)(secretWords[0] xor secretWords[1]) + seed) xor (u64)combined;
|
||||
return avalanche_XXH64(value);
|
||||
|
||||
XXH3_128_1to3():
|
||||
u32 secretWords[4] = secret[0:16];
|
||||
u64 low = ((u64)(secretWords[0] xor secretWords[1]) + seed) xor (u64)combined;
|
||||
u64 high = ((u64)(secretWords[2] xor secretWords[3]) - seed) xor (u64)(bswap32(combined) <<< 13);
|
||||
// note that the bswap32(combined) <<< 13 above is 32-bit rotate
|
||||
return {avalanche_XXH64(low), // lower half
|
||||
avalanche_XXH64(high)}; // higher half
|
||||
```
|
||||
|
||||
Note that the XXH3-64 result is the lower half of XXH3-128 result.
|
||||
|
||||
### 4-8 bytes of input
|
||||
|
||||
The algorithm starts from reading the first and last 4 bytes of the input as little-endian 32-bit values, and a modified seed:
|
||||
|
||||
```c
|
||||
u32 inputFirst = input[0:4];
|
||||
u32 inputLast = input[inputLength-4:inputLength];
|
||||
u64 modifiedSeed = seed xor ((u64)bswap32((u32)lowerHalf(seed)) << 32);
|
||||
```
|
||||
|
||||
Again, these values are combined with a segment of the secret to produce the final value.
|
||||
|
||||
```c
|
||||
XXH3_64_4to8():
|
||||
u64 secretWords[2] = secret[8:24];
|
||||
u64 combined = (u64)inputLast | ((u64)inputFirst << 32);
|
||||
u64 value = ((secretWords[0] xor secretWords[1]) - modifiedSeed) xor combined;
|
||||
value = value xor (value <<< 49) xor (value <<< 24);
|
||||
value = value * PRIME_MX2;
|
||||
value = value xor ((value >> 35) + inputLength);
|
||||
value = value * PRIME_MX2;
|
||||
value = value xor (value >> 28);
|
||||
return value;
|
||||
|
||||
XXH3_128_4to8():
|
||||
u64 secretWords[2] = secret[16:32];
|
||||
u64 combined = (u64)inputFirst | ((u64)inputLast << 32);
|
||||
u64 value = ((secretWords[0] xor secretWords[1]) + modifiedSeed) xor combined;
|
||||
u128 mulResult = (u128)value * (u128)(PRIME64_1 + (inputLength << 2));
|
||||
u64 high = higherHalf(mulResult); // mulResult >> 64
|
||||
u64 low = lowerHalf(mulResult); // mulResult & 0xFFFFFFFFFFFFFFFF
|
||||
high = high + (low << 1);
|
||||
low = low xor (high >> 3);
|
||||
low = low xor (low >> 35);
|
||||
low = low * PRIME_MX2;
|
||||
low = low xor (low >> 28);
|
||||
high = avalanche(high);
|
||||
return {low, high};
|
||||
```
|
||||
|
||||
### 9-16 bytes of input
|
||||
|
||||
The algorithm starts from reading the first and last 8 bytes of the input as little-endian 64-bit values:
|
||||
|
||||
```c
|
||||
u64 inputFirst = input[0:8];
|
||||
u64 inputLast = input[inputLength-8:inputLength];
|
||||
```
|
||||
|
||||
Once again, these values are combined with a segment of the secret to produce the final value.
|
||||
|
||||
```c
|
||||
XXH3_64_9to16():
|
||||
u64 secretWords[4] = secret[24:56];
|
||||
u64 low = ((secretWords[0] xor secretWords[1]) + seed) xor inputFirst;
|
||||
u64 high = ((secretWords[2] xor secretWords[3]) - seed) xor inputLast;
|
||||
u128 mulResult = (u128)low * (u128)high;
|
||||
u64 value = inputLength + bswap64(low) + high + (u64)(lowerHalf(mulResult) xor higherHalf(mulResult));
|
||||
return avalanche(value);
|
||||
|
||||
XXH3_128_9to16():
|
||||
u64 secretWords[4] = secret[32:64];
|
||||
u64 val1 = ((secretWords[0] xor secretWords[1]) - seed) xor inputFirst xor inputLast;
|
||||
u64 val2 = ((secretWords[2] xor secretWords[3]) + seed) xor inputLast;
|
||||
u128 mulResult = (u128)val1 * (u128)PRIME64_1;
|
||||
u64 low = lowerHalf(mulResult) + ((u64)(inputLength - 1) << 54);
|
||||
u64 high = higherHalf(mulResult) + ((u64)higherHalf(val2) << 32) + (u64)lowerHalf(val2) * PRIME32_2;
|
||||
// the above line can also be simplified to higherHalf(mulResult) + val2 + (u64)lowerHalf(val2) * (PRIME32_2 - 1);
|
||||
low = low xor bswap64(high);
|
||||
// the following three lines are in fact a 128x64 -> 128 multiplication ({low,high} = (u128){low,high} * PRIME64_2)
|
||||
u128 mulResult2 = (u128)low * (u128)PRIME64_2;
|
||||
low = lowerHalf(mulResult2);
|
||||
high = higherHalf(mulResult2) + high * PRIME64_2;
|
||||
return {avalanche(low), // lower half
|
||||
avalanche(high)}; // higher half
|
||||
```
|
||||
|
||||
|
||||
XXH3 Algorithm Description (for medium inputs)
|
||||
-------------------------------------
|
||||
|
||||
This algorithm is used for medium inputs (17-240 bytes of input). Its internal hash state is stored inside 1 (XXH3-64) or 2 (XXH3-128) "accumulators", each storing an unsigned 64-bit value.
|
||||
|
||||
### Step 1. Initialize internal accumulators
|
||||
|
||||
The accumulator(s) are initialized based on the input length.
|
||||
|
||||
```c
|
||||
// For XXH3-64
|
||||
u64 acc = inputLength * PRIME64_1;
|
||||
|
||||
// For XXH3-128
|
||||
u64 acc[2] = {inputLength * PRIME64_1, 0};
|
||||
```
|
||||
|
||||
### Step 2. Process the input
|
||||
|
||||
This step is further divided into two cases: one for 17-128 bytes of input, and one for 129-240 bytes of input.
|
||||
|
||||
#### Mixing operation
|
||||
|
||||
This step uses a mixing operation that mixes a 16-byte segment of data, a 16-byte segment of secret and the seed into a 64-bit value as a building block. This operation treat the segment of data and secret as little-endian 64-bit values.
|
||||
|
||||
```c
|
||||
mixStep(u8 data[16], size secretOffset, u64 seed):
|
||||
u64 dataWords[2] = data[0:16];
|
||||
u64 secretWords[2] = secret[secretOffset:secretOffset+16];
|
||||
u128 mulResult = (u128)(dataWords[0] xor (secretWords[0] + seed)) *
|
||||
(u128)(dataWords[1] xor (secretWords[1] - seed));
|
||||
return lowerHalf(mulResult) xor higherHalf(mulResult);
|
||||
```
|
||||
|
||||
The mixing operation is always invoked in groups of two in XXH3-128, where two 16-byte segments of data are mixed with a 32-byte segment of secret, and the accumulators are updated accordingly.
|
||||
|
||||
```c
|
||||
mixTwoChunks(u8 data1[16], u8 data2[16], size secretOffset, u64 seed):
|
||||
u64 dataWords1[2] = data1[0:16]; // again, little-endian conversion
|
||||
u64 dataWords2[2] = data2[0:16];
|
||||
acc[0] = acc[0] + mixStep(data1, secretOffset, seed);
|
||||
acc[1] = acc[1] + mixStep(data2, secretOffset + 16, seed);
|
||||
acc[0] = acc[0] xor (dataWords2[0] + dataWords2[1]);
|
||||
acc[1] = acc[1] xor (dataWords1[0] + dataWords1[1]);
|
||||
```
|
||||
|
||||
The input is split into several 16-byte chunks and mixed, and the result is added to the accumulator(s).
|
||||
|
||||
#### 17-128 bytes of input
|
||||
|
||||
The input is read as *N* 16-byte chunks starting from the beginning and *N* chunks starting from the end, where *N* is the smallest number that these 2*N* chunks cover the whole input. These chunks are paired up and mixed, and the results are accumulated to the accumulator(s).
|
||||
|
||||
```c
|
||||
// the loop variable `i` should be signed to avoid underflow in implementation
|
||||
processInput_XXH3_64_17to128():
|
||||
u64 numRounds = ((inputLength - 1) >> 5) + 1;
|
||||
for (i = numRounds - 1; i >= 0; i--) {
|
||||
size offsetStart = i*16;
|
||||
size offsetEnd = inputLength - i*16 - 16;
|
||||
acc += mixStep(input[offsetStart:offsetStart+16], i*32, seed);
|
||||
acc += mixStep(input[offsetEnd:offsetEnd+16], i*32+16, seed);
|
||||
}
|
||||
|
||||
processInput_XXH3_128_17to128():
|
||||
u64 numRounds = ((inputLength - 1) >> 5) + 1;
|
||||
for (i = numRounds - 1; i >= 0; i--) {
|
||||
size offsetStart = i*16;
|
||||
size offsetEnd = inputLength - i*16 - 16;
|
||||
mixTwoChunks(input[offsetStart:offsetStart+16], input[offsetEnd:offsetEnd+16], i*32, seed);
|
||||
}
|
||||
```
|
||||
|
||||
#### 129-240 bytes of input
|
||||
|
||||
The input is split into 16-byte (XXH3-64) or 32-byte (XXH3-128) chunks. The first 128 bytes are first mixed chunk by chunk, followed by an intermediate avalanche operation. Then the remaining full chunks are processed, and finally the last 16/32 bytes are treated as a chunk to process.
|
||||
|
||||
```c
|
||||
processInput_XXH3_64_129to240():
|
||||
u64 numChunks = inputLength >> 4;
|
||||
for (i = 0; i < 8; i++) {
|
||||
acc += mixStep(input[i*16:i*16+16], i*16, seed);
|
||||
}
|
||||
acc = avalanche(acc);
|
||||
for (i = 8; i < numChunks; i++) {
|
||||
acc += mixStep(input[i*16:i*16+16], (i-8)*16 + 3, seed);
|
||||
}
|
||||
acc += mixStep(input[inputLength-16:inputLength], 119, seed);
|
||||
|
||||
processInput_XXH3_128_129to240():
|
||||
u64 numChunks = inputLength >> 5;
|
||||
for (i = 0; i < 4; i++) {
|
||||
mixTwoChunks(input[i*32:i*32+16], input[i*32+16:i*32+32], i*32, seed);
|
||||
}
|
||||
acc[0] = avalanche(acc[0]);
|
||||
acc[1] = avalanche(acc[1]);
|
||||
for (i = 4; i < numChunks; i++) {
|
||||
mixTwoChunks(input[i*32:i*32+16], input[i*32+16:i*32+32], (i-4)*32 + 3, seed);
|
||||
}
|
||||
// note that the half-chunk order and the seed is different here
|
||||
mixTwoChunks(input[inputLength-16:inputLength], input[inputLength-32:inputLength-16], 103, (u64)0 - seed);
|
||||
```
|
||||
|
||||
### Step 3. Finalization
|
||||
|
||||
The final result is extracted from the accumulator(s).
|
||||
|
||||
```c
|
||||
XXH3_64_17to240():
|
||||
return avalanche(acc);
|
||||
|
||||
XXH3_128_17to240():
|
||||
u64 low = acc[0] + acc[1];
|
||||
u64 high = (acc[0] * PRIME64_1) + (acc[1] * PRIME64_4) + (((u64)inputLength - seed) * PRIME64_2);
|
||||
return {avalanche(low), // lower half
|
||||
(u64)0 - avalanche(high)}; // higher half
|
||||
```
|
||||
|
||||
XXH3 Algorithm Description (for large inputs)
|
||||
-------------------------------------
|
||||
|
||||
This algorithm is used for inputs larger than 240 bytes. The internal hash state is stored inside 8 "accumulators", each one storing an unsigned 64-bit value.
|
||||
|
||||
### Step 1. Initialize internal accumulators
|
||||
|
||||
The accumulators are initialized to fixed constants:
|
||||
|
||||
```c
|
||||
u64 acc[8] = {
|
||||
PRIME32_3, PRIME64_1, PRIME64_2, PRIME64_3,
|
||||
PRIME64_4, PRIME32_2, PRIME64_5, PRIME32_1};
|
||||
```
|
||||
|
||||
### Step 2. Process blocks
|
||||
|
||||
The input is consumed and processed one full block at a time. The size of the block depends on the length of the secret. Specifically, a block consists of several 64-byte stripes. The number of stripes per block is `floor((secretLength-64)/8)` . For the default 192-byte secret, there are 16 stripes in a block, and thus the block size is 1024 bytes.
|
||||
|
||||
```c
|
||||
secretLength = lengthInBytes(secret); // default 192; at least 136
|
||||
stripesPerBlock = (secretLength-64) / 8; // default 16; at least 9
|
||||
blockSize = 64 * stripesPerBlock; // default 1024; at least 576
|
||||
```
|
||||
|
||||
The process of processing a full block is called a *round*. It consists of the following two sub-steps:
|
||||
|
||||
#### Step 2-1. Process stripes in the block
|
||||
|
||||
A stripe is evenly divided into 8 lanes, of 8 bytes each. In an accumulation step, one stripe and a 64-byte contiguous segment of the secret are used to update the accumulators. Each lane reads its associated 64-bit value using little-endian convention.
|
||||
|
||||
The accumulation step applies the following procedure:
|
||||
|
||||
```c
|
||||
accumulate(u64 stripe[8], size secretOffset):
|
||||
u64 secretWords[8] = secret[secretOffset:secretOffset+64];
|
||||
for (i = 0; i < 8; i++) {
|
||||
u64 value = stripe[i] xor secretWords[i];
|
||||
acc[i xor 1] = acc[i xor 1] + stripe[i];
|
||||
acc[i] = acc[i] + (u64)lowerHalf(value) * (u64)higherHalf(value);
|
||||
// (value and 0xFFFFFFFF) * (value >> 32)
|
||||
}
|
||||
```
|
||||
|
||||
The accumulation step is repeated for all stripes in a block, using different segments of the secret, starting from the first 64 bytes for the first stripe, and offset by 8 bytes for each following round:
|
||||
|
||||
```c
|
||||
round_accumulate(u8 block[blockSize]):
|
||||
for (n = 0; n < stripesPerBlock; n++) {
|
||||
u64 stripe[8] = block[n*64:n*64+64]; // 64 bytes = 8 u64s
|
||||
accumulate(stripe, n*8);
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 2-2. Scramble accumulators
|
||||
|
||||
After the accumulation steps are finished for all stripes in the block, the accumulators are scrambled using the last 64 bytes of the secret.
|
||||
|
||||
```c
|
||||
round_scramble():
|
||||
u64 secretWords[8] = secret[secretLength-64:secretLength];
|
||||
for (i = 0; i < 8; i++) {
|
||||
acc[i] = acc[i] xor (acc[i] >> 47);
|
||||
acc[i] = acc[i] xor secretWords[i];
|
||||
acc[i] = acc[i] * PRIME32_1;
|
||||
}
|
||||
```
|
||||
|
||||
A round is thus a `round_accumulate` followed by a `round_scramble`:
|
||||
|
||||
```c
|
||||
round(u8 block[blockSize]):
|
||||
round_accumulate(block);
|
||||
round_scramble();
|
||||
```
|
||||
|
||||
Step 2 is looped to consume the input until there are less than or equal to `blockSize` bytes of input left. Note that we leave the last block to the next step even if it is a full block.
|
||||
|
||||
### Step 3. Process the last block and the last 64 bytes
|
||||
|
||||
Accumulation steps are run for the stripes in the last block, except for the last stripe (whether it is full or not). After that, run a final accumulation step by treating the last 64 bytes as a stripe. Note that the last 64 bytes might overlap with the second-to-last block.
|
||||
|
||||
```c
|
||||
// len is the size of the last block (1 <= len <= blockSize)
|
||||
lastRound(u8 block[], size len, u64 lastStripe[8]):
|
||||
size nFullStripes = (len-1)/64;
|
||||
for (n = 0; n < nFullStripes; n++) {
|
||||
u64 stripe[8] = block[n*64:n*64+64];
|
||||
accumulate(stripe, n * 8);
|
||||
}
|
||||
accumulate(lastStripe, secretLength - 71);
|
||||
```
|
||||
|
||||
### Step 4. Finalization
|
||||
|
||||
In the finalization step, a merging procedure is used to extract a single 64-bit value from the accumulators, using an initial seed value and a 64-byte segment of the secret.
|
||||
|
||||
```c
|
||||
finalMerge(u64 initValue, size secretOffset):
|
||||
u64 secretWords[8] = secret[secretOffset:secretOffset+64];
|
||||
u64 result = initValue;
|
||||
for (i = 0; i < 4; i++) {
|
||||
// 64-bit by 64-bit multiplication to 128-bit full result
|
||||
u128 mulResult = (u128)(acc[i*2] xor secretWords[i*2]) *
|
||||
(u128)(acc[i*2+1] xor secretWords[i*2+1]);
|
||||
result = result + (lowerHalf(mulResult) xor higherHalf(mulResult));
|
||||
// (mulResult and 0xFFFFFFFFFFFFFFFF) xor (mulResult >> 64)
|
||||
}
|
||||
return avalanche(result);
|
||||
```
|
||||
|
||||
XXH3-128 runs the merging procedure twice for the two halves of the result, using different secret segments and different initial values derived from the total input length.
|
||||
The XXH3-64 result is just the lower half of the XXH3-128 result.
|
||||
|
||||
```c
|
||||
XXH3_64_large():
|
||||
return finalMerge((u64)inputLength * PRIME64_1, 11);
|
||||
|
||||
XXH3_128_large():
|
||||
return {finalMerge((u64)inputLength * PRIME64_1, 11), // lower half
|
||||
finalMerge(~((u64)inputLength * PRIME64_2), secretLength - 75)}; // higher half
|
||||
```
|
||||
|
||||
|
||||
Performance considerations
|
||||
----------------------------------
|
||||
|
||||
The xxHash algorithms are simple and compact to implement. They provide a system independent "fingerprint" or digest of a message of arbitrary length.
|
||||
|
||||
The algorithm allows input to be streamed and processed in multiple steps. In such case, an internal buffer is needed to ensure data is presented to the algorithm in full stripes.
|
||||
|
||||
On 64-bit systems, the 64-bit variant `XXH64` is generally faster to compute, so it is a recommended variant, even when only 32-bit are needed.
|
||||
|
||||
On 32-bit systems though, positions are reversed: `XXH64` performance is reduced, due to its usage of 64-bit arithmetic. `XXH32` becomes a faster variant.
|
||||
|
||||
Finally, when vector operations are possible, `XXH3` is likely the faster variant.
|
||||
|
||||
|
||||
Reference Implementation
|
||||
----------------------------------------
|
||||
|
||||
A reference library written in C is available at https://www.xxhash.com.
|
||||
The web page also links to multiple other implementations written in many different languages.
|
||||
It links to the [github project page](https://github.com/Cyan4973/xxHash) where an [issue board](https://github.com/Cyan4973/xxHash/issues) can be used for further public discussions on the topic.
|
||||
|
||||
|
||||
Version changes
|
||||
--------------------
|
||||
v0.2.0: added XXH3 specification, by Adrien Wu
|
||||
v0.1.1: added a note on rationale for selection of constants
|
||||
v0.1.0: initial release
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* xxHash - Extremely Fast Hash algorithm
|
||||
* Development source file for `xxh3`
|
||||
* Copyright (C) 2019-2021 Yann Collet
|
||||
*
|
||||
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* You can contact the author at:
|
||||
* - xxHash homepage: https://www.xxhash.com
|
||||
* - xxHash source repository: https://github.com/Cyan4973/xxHash
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note: This file used to host the source code of XXH3_* variants.
|
||||
* during the development period.
|
||||
* The source code is now properly integrated within xxhash.h.
|
||||
*
|
||||
* xxh3.h is no longer useful,
|
||||
* but it is still provided for compatibility with source code
|
||||
* which used to include it directly.
|
||||
*
|
||||
* Programs are now highly discouraged to include xxh3.h.
|
||||
* Include `xxhash.h` instead, which is the officially supported interface.
|
||||
*
|
||||
* In the future, xxh3.h will start to generate warnings, then errors,
|
||||
* then it will be removed from source package and from include directory.
|
||||
*/
|
||||
|
||||
/* Simulate the same impact as including the old xxh3.h source file */
|
||||
|
||||
#define XXH_INLINE_ALL
|
||||
#include "xxhash.h"
|
||||
@@ -1,845 +0,0 @@
|
||||
/*
|
||||
* xxHash - Extremely Fast Hash algorithm
|
||||
* Copyright (C) 2020-2021 Yann Collet
|
||||
*
|
||||
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* You can contact the author at:
|
||||
* - xxHash homepage: https://www.xxhash.com
|
||||
* - xxHash source repository: https://github.com/Cyan4973/xxHash
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
* @file xxh_x86dispatch.c
|
||||
*
|
||||
* Automatic dispatcher code for the @ref XXH3_family on x86-based targets.
|
||||
*
|
||||
* Optional add-on.
|
||||
*
|
||||
* **Compile this file with the default flags for your target.** Do not compile
|
||||
* with flags like `-mavx*`, `-march=native`, or `/arch:AVX*`, there will be
|
||||
* an error. See @ref XXH_X86DISPATCH_ALLOW_AVX for details.
|
||||
*
|
||||
* @defgroup dispatch x86 Dispatcher
|
||||
* @{
|
||||
*/
|
||||
|
||||
#if defined (__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if !(defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64))
|
||||
# error "Dispatching is currently only supported on x86 and x86_64."
|
||||
#endif
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
#ifndef XXH_HAS_INCLUDE
|
||||
# ifdef __has_include
|
||||
# define XXH_HAS_INCLUDE(x) __has_include(x)
|
||||
# else
|
||||
# define XXH_HAS_INCLUDE(x) 0
|
||||
# endif
|
||||
#endif
|
||||
/*! @endcond */
|
||||
|
||||
/*!
|
||||
* @def XXH_X86DISPATCH_ALLOW_AVX
|
||||
* @brief Disables the AVX sanity check.
|
||||
*
|
||||
* xxh_x86dispatch.c is intended to be compiled for the minimum target, and
|
||||
* it selectively enables SSE2, AVX2, and AVX512 when it is needed.
|
||||
*
|
||||
* Compiling with options like `-mavx*`, `-march=native`, or `/arch:AVX*`
|
||||
* _globally_ will always enable this feature, and therefore makes it
|
||||
* undefined behavior to execute on any CPU without said feature.
|
||||
*
|
||||
* Even if the source code isn't directly using AVX intrinsics in a function,
|
||||
* the compiler can still generate AVX code from autovectorization and by
|
||||
* "upgrading" SSE2 intrinsics to use the VEX prefixes (a.k.a. AVX128).
|
||||
*
|
||||
* Define XXH_X86DISPATCH_ALLOW_AVX to ignore this check,
|
||||
* thus accepting that the produced binary will not work correctly
|
||||
* on any CPU with less features than the ones stated at compilation time.
|
||||
*/
|
||||
#ifdef XXH_DOXYGEN
|
||||
# define XXH_X86DISPATCH_ALLOW_AVX
|
||||
#endif
|
||||
|
||||
#if defined(__AVX__) && !defined(XXH_X86DISPATCH_ALLOW_AVX)
|
||||
# error "Error: if xxh_x86dispatch.c is compiled with AVX enabled, the resulting binary will crash on sse2-only cpus !! " \
|
||||
"If you nonetheless want to do that, please enable the XXH_X86DISPATCH_ALLOW_AVX build variable"
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* @def XXH_DISPATCH_SCALAR
|
||||
* @brief Enables/dispatching the scalar code path.
|
||||
*
|
||||
* If this is defined to 0, SSE2 support is assumed. This reduces code size
|
||||
* when the scalar path is not needed.
|
||||
*
|
||||
* This is automatically defined to 0 when...
|
||||
* - SSE2 support is enabled in the compiler
|
||||
* - Targeting x86_64
|
||||
* - Targeting Android x86
|
||||
* - Targeting macOS
|
||||
*/
|
||||
#ifndef XXH_DISPATCH_SCALAR
|
||||
# if defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) /* SSE2 on by default */ \
|
||||
|| defined(__x86_64__) || defined(_M_X64) /* x86_64 */ \
|
||||
|| defined(__ANDROID__) || defined(__APPLE__) /* Android or macOS */
|
||||
# define XXH_DISPATCH_SCALAR 0 /* disable */
|
||||
# else
|
||||
# define XXH_DISPATCH_SCALAR 1
|
||||
# endif
|
||||
#endif
|
||||
/*!
|
||||
* @def XXH_DISPATCH_AVX2
|
||||
* @brief Enables/disables dispatching for AVX2.
|
||||
*
|
||||
* This is automatically detected if it is not defined.
|
||||
* - GCC 4.7 and later are known to support AVX2, but >4.9 is required for
|
||||
* to get the AVX2 intrinsics and typedefs without -mavx -mavx2.
|
||||
* - Visual Studio 2013 Update 2 and later are known to support AVX2.
|
||||
* - The GCC/Clang internal header `<avx2intrin.h>` is detected. While this is
|
||||
* not allowed to be included directly, it still appears in the builtin
|
||||
* include path and is detectable with `__has_include`.
|
||||
*
|
||||
* @see XXH_AVX2
|
||||
*/
|
||||
#ifndef XXH_DISPATCH_AVX2
|
||||
# if (defined(__GNUC__) && (__GNUC__ > 4)) /* GCC 5.0+ */ \
|
||||
|| (defined(_MSC_VER) && _MSC_VER >= 1900) /* VS 2015+ */ \
|
||||
|| (defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 180030501) /* VS 2013 Update 2 */ \
|
||||
|| XXH_HAS_INCLUDE(<avx2intrin.h>) /* GCC/Clang internal header */
|
||||
# define XXH_DISPATCH_AVX2 1 /* enable dispatch towards AVX2 */
|
||||
# else
|
||||
# define XXH_DISPATCH_AVX2 0
|
||||
# endif
|
||||
#endif /* XXH_DISPATCH_AVX2 */
|
||||
|
||||
/*!
|
||||
* @def XXH_DISPATCH_AVX512
|
||||
* @brief Enables/disables dispatching for AVX512.
|
||||
*
|
||||
* Automatically detected if one of the following conditions is met:
|
||||
* - GCC 4.9 and later are known to support AVX512.
|
||||
* - Visual Studio 2017 and later are known to support AVX2.
|
||||
* - The GCC/Clang internal header `<avx512fintrin.h>` is detected. While this
|
||||
* is not allowed to be included directly, it still appears in the builtin
|
||||
* include path and is detectable with `__has_include`.
|
||||
*
|
||||
* @see XXH_AVX512
|
||||
*/
|
||||
#ifndef XXH_DISPATCH_AVX512
|
||||
# if (defined(__GNUC__) \
|
||||
&& (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))) /* GCC 4.9+ */ \
|
||||
|| (defined(_MSC_VER) && _MSC_VER >= 1910) /* VS 2017+ */ \
|
||||
|| XXH_HAS_INCLUDE(<avx512fintrin.h>) /* GCC/Clang internal header */
|
||||
# define XXH_DISPATCH_AVX512 1 /* enable dispatch towards AVX512 */
|
||||
# else
|
||||
# define XXH_DISPATCH_AVX512 0
|
||||
# endif
|
||||
#endif /* XXH_DISPATCH_AVX512 */
|
||||
|
||||
/*!
|
||||
* @def XXH_TARGET_SSE2
|
||||
* @brief Allows a function to be compiled with SSE2 intrinsics.
|
||||
*
|
||||
* Uses `__attribute__((__target__("sse2")))` on GCC to allow SSE2 to be used
|
||||
* even with `-mno-sse2`.
|
||||
*
|
||||
* @def XXH_TARGET_AVX2
|
||||
* @brief Like @ref XXH_TARGET_SSE2, but for AVX2.
|
||||
*
|
||||
* @def XXH_TARGET_AVX512
|
||||
* @brief Like @ref XXH_TARGET_SSE2, but for AVX512.
|
||||
*
|
||||
*/
|
||||
#if defined(__GNUC__)
|
||||
# include <emmintrin.h> /* SSE2 */
|
||||
# if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
|
||||
# include <immintrin.h> /* AVX2, AVX512F */
|
||||
# endif
|
||||
# define XXH_TARGET_SSE2 __attribute__((__target__("sse2")))
|
||||
# define XXH_TARGET_AVX2 __attribute__((__target__("avx2")))
|
||||
# define XXH_TARGET_AVX512 __attribute__((__target__("avx512f")))
|
||||
#elif defined(__clang__) && defined(_MSC_VER) /* clang-cl.exe */
|
||||
# include <emmintrin.h> /* SSE2 */
|
||||
# if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
|
||||
# include <immintrin.h> /* AVX2, AVX512F */
|
||||
# include <smmintrin.h>
|
||||
# include <avxintrin.h>
|
||||
# include <avx2intrin.h>
|
||||
# include <avx512fintrin.h>
|
||||
# endif
|
||||
# define XXH_TARGET_SSE2 __attribute__((__target__("sse2")))
|
||||
# define XXH_TARGET_AVX2 __attribute__((__target__("avx2")))
|
||||
# define XXH_TARGET_AVX512 __attribute__((__target__("avx512f")))
|
||||
#elif defined(_MSC_VER)
|
||||
# include <intrin.h>
|
||||
# define XXH_TARGET_SSE2
|
||||
# define XXH_TARGET_AVX2
|
||||
# define XXH_TARGET_AVX512
|
||||
#else
|
||||
# error "Dispatching is currently not supported for your compiler."
|
||||
#endif
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
#ifdef XXH_DISPATCH_DEBUG
|
||||
/* debug logging */
|
||||
# include <stdio.h>
|
||||
# define XXH_debugPrint(str) { fprintf(stderr, "DEBUG: xxHash dispatch: %s \n", str); fflush(NULL); }
|
||||
#else
|
||||
# define XXH_debugPrint(str) ((void)0)
|
||||
# undef NDEBUG /* avoid redefinition */
|
||||
# define NDEBUG
|
||||
#endif
|
||||
/*! @endcond */
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef XXH_DOXYGEN
|
||||
#define XXH_INLINE_ALL
|
||||
#define XXH_X86DISPATCH
|
||||
#include "xxhash.h"
|
||||
#endif
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
#ifndef XXH_HAS_ATTRIBUTE
|
||||
# ifdef __has_attribute
|
||||
# define XXH_HAS_ATTRIBUTE(...) __has_attribute(__VA_ARGS__)
|
||||
# else
|
||||
# define XXH_HAS_ATTRIBUTE(...) 0
|
||||
# endif
|
||||
#endif
|
||||
/*! @endcond */
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
#if XXH_HAS_ATTRIBUTE(constructor)
|
||||
# define XXH_CONSTRUCTOR __attribute__((constructor))
|
||||
# define XXH_DISPATCH_MAYBE_NULL 0
|
||||
#else
|
||||
# define XXH_CONSTRUCTOR
|
||||
# define XXH_DISPATCH_MAYBE_NULL 1
|
||||
#endif
|
||||
/*! @endcond */
|
||||
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
/*
|
||||
* Support both AT&T and Intel dialects
|
||||
*
|
||||
* GCC doesn't convert AT&T syntax to Intel syntax, and will error out if
|
||||
* compiled with -masm=intel. Instead, it supports dialect switching with
|
||||
* curly braces: { AT&T syntax | Intel syntax }
|
||||
*
|
||||
* Clang's integrated assembler automatically converts AT&T syntax to Intel if
|
||||
* needed, making the dialect switching useless (it isn't even supported).
|
||||
*
|
||||
* Note: Comments are written in the inline assembly itself.
|
||||
*/
|
||||
#ifdef __clang__
|
||||
# define XXH_I_ATT(intel, att) att "\n\t"
|
||||
#else
|
||||
# define XXH_I_ATT(intel, att) "{" att "|" intel "}\n\t"
|
||||
#endif
|
||||
/*! @endcond */
|
||||
|
||||
/*!
|
||||
* @private
|
||||
* @brief Runs CPUID.
|
||||
*
|
||||
* @param eax , ecx The parameters to pass to CPUID, %eax and %ecx respectively.
|
||||
* @param abcd The array to store the result in, `{ eax, ebx, ecx, edx }`
|
||||
*/
|
||||
static void XXH_cpuid(xxh_u32 eax, xxh_u32 ecx, xxh_u32* abcd)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
__cpuidex((int*)abcd, eax, ecx);
|
||||
#else
|
||||
xxh_u32 ebx, edx;
|
||||
# if defined(__i386__) && defined(__PIC__)
|
||||
__asm__(
|
||||
"# Call CPUID\n\t"
|
||||
"#\n\t"
|
||||
"# On 32-bit x86 with PIC enabled, we are not allowed to overwrite\n\t"
|
||||
"# EBX, so we use EDI instead.\n\t"
|
||||
XXH_I_ATT("mov edi, ebx", "movl %%ebx, %%edi")
|
||||
XXH_I_ATT("cpuid", "cpuid" )
|
||||
XXH_I_ATT("xchg edi, ebx", "xchgl %%ebx, %%edi")
|
||||
: "=D" (ebx),
|
||||
# else
|
||||
__asm__(
|
||||
"# Call CPUID\n\t"
|
||||
XXH_I_ATT("cpuid", "cpuid")
|
||||
: "=b" (ebx),
|
||||
# endif
|
||||
"+a" (eax), "+c" (ecx), "=d" (edx));
|
||||
abcd[0] = eax;
|
||||
abcd[1] = ebx;
|
||||
abcd[2] = ecx;
|
||||
abcd[3] = edx;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Modified version of Intel's guide
|
||||
* https://software.intel.com/en-us/articles/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family
|
||||
*/
|
||||
|
||||
#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
|
||||
/*!
|
||||
* @private
|
||||
* @brief Runs `XGETBV`.
|
||||
*
|
||||
* While the CPU may support AVX2, the operating system might not properly save
|
||||
* the full YMM/ZMM registers.
|
||||
*
|
||||
* xgetbv is used for detecting this: Any compliant operating system will define
|
||||
* a set of flags in the xcr0 register indicating how it saves the AVX registers.
|
||||
*
|
||||
* You can manually disable this flag on Windows by running, as admin:
|
||||
*
|
||||
* bcdedit.exe /set xsavedisable 1
|
||||
*
|
||||
* and rebooting. Run the same command with 0 to re-enable it.
|
||||
*/
|
||||
static xxh_u64 XXH_xgetbv(void)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
return _xgetbv(0); /* min VS2010 SP1 compiler is required */
|
||||
#else
|
||||
xxh_u32 xcr0_lo, xcr0_hi;
|
||||
__asm__(
|
||||
"# Call XGETBV\n\t"
|
||||
"#\n\t"
|
||||
"# Older assemblers (e.g. macOS's ancient GAS version) don't support\n\t"
|
||||
"# the XGETBV opcode, so we encode it by hand instead.\n\t"
|
||||
"# See <https://github.com/asmjit/asmjit/issues/78> for details.\n\t"
|
||||
".byte 0x0f, 0x01, 0xd0\n\t"
|
||||
: "=a" (xcr0_lo), "=d" (xcr0_hi) : "c" (0));
|
||||
return xcr0_lo | ((xxh_u64)xcr0_hi << 32);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
#define XXH_SSE2_CPUID_MASK (1 << 26)
|
||||
#define XXH_OSXSAVE_CPUID_MASK ((1 << 26) | (1 << 27))
|
||||
#define XXH_AVX2_CPUID_MASK (1 << 5)
|
||||
#define XXH_AVX2_XGETBV_MASK ((1 << 2) | (1 << 1))
|
||||
#define XXH_AVX512F_CPUID_MASK (1 << 16)
|
||||
#define XXH_AVX512F_XGETBV_MASK ((7 << 5) | (1 << 2) | (1 << 1))
|
||||
/*! @endcond */
|
||||
|
||||
/*!
|
||||
* @private
|
||||
* @brief Returns the best XXH3 implementation.
|
||||
*
|
||||
* Runs various CPUID/XGETBV tests to try and determine the best implementation.
|
||||
*
|
||||
* @return The best @ref XXH_VECTOR implementation.
|
||||
* @see XXH_VECTOR_TYPES
|
||||
*/
|
||||
static int XXH_featureTest(void)
|
||||
{
|
||||
xxh_u32 abcd[4];
|
||||
xxh_u32 max_leaves;
|
||||
int best = XXH_SCALAR;
|
||||
#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
|
||||
xxh_u64 xgetbv_val;
|
||||
#endif
|
||||
#if defined(__GNUC__) && defined(__i386__)
|
||||
xxh_u32 cpuid_supported;
|
||||
__asm__(
|
||||
"# For the sake of ruthless backwards compatibility, check if CPUID\n\t"
|
||||
"# is supported in the EFLAGS on i386.\n\t"
|
||||
"# This is not necessary on x86_64 - CPUID is mandatory.\n\t"
|
||||
"# The ID flag (bit 21) in the EFLAGS register indicates support\n\t"
|
||||
"# for the CPUID instruction. If a software procedure can set and\n\t"
|
||||
"# clear this flag, the processor executing the procedure supports\n\t"
|
||||
"# the CPUID instruction.\n\t"
|
||||
"# <https://c9x.me/x86/html/file_module_x86_id_45.html>\n\t"
|
||||
"#\n\t"
|
||||
"# Routine is from <https://wiki.osdev.org/CPUID>.\n\t"
|
||||
|
||||
"# Save EFLAGS\n\t"
|
||||
XXH_I_ATT("pushfd", "pushfl" )
|
||||
"# Store EFLAGS\n\t"
|
||||
XXH_I_ATT("pushfd", "pushfl" )
|
||||
"# Invert the ID bit in stored EFLAGS\n\t"
|
||||
XXH_I_ATT("xor dword ptr[esp], 0x200000", "xorl $0x200000, (%%esp)")
|
||||
"# Load stored EFLAGS (with ID bit inverted)\n\t"
|
||||
XXH_I_ATT("popfd", "popfl" )
|
||||
"# Store EFLAGS again (ID bit may or not be inverted)\n\t"
|
||||
XXH_I_ATT("pushfd", "pushfl" )
|
||||
"# eax = modified EFLAGS (ID bit may or may not be inverted)\n\t"
|
||||
XXH_I_ATT("pop eax", "popl %%eax" )
|
||||
"# eax = whichever bits were changed\n\t"
|
||||
XXH_I_ATT("xor eax, dword ptr[esp]", "xorl (%%esp), %%eax" )
|
||||
"# Restore original EFLAGS\n\t"
|
||||
XXH_I_ATT("popfd", "popfl" )
|
||||
"# eax = zero if ID bit can't be changed, else non-zero\n\t"
|
||||
XXH_I_ATT("and eax, 0x200000", "andl $0x200000, %%eax" )
|
||||
: "=a" (cpuid_supported) :: "cc");
|
||||
|
||||
if (XXH_unlikely(!cpuid_supported)) {
|
||||
XXH_debugPrint("CPUID support is not detected!");
|
||||
return best;
|
||||
}
|
||||
|
||||
#endif
|
||||
/* Check how many CPUID pages we have */
|
||||
XXH_cpuid(0, 0, abcd);
|
||||
max_leaves = abcd[0];
|
||||
|
||||
/* Shouldn't happen on hardware, but happens on some QEMU configs. */
|
||||
if (XXH_unlikely(max_leaves == 0)) {
|
||||
XXH_debugPrint("Max CPUID leaves == 0!");
|
||||
return best;
|
||||
}
|
||||
|
||||
/* Check for SSE2, OSXSAVE and xgetbv */
|
||||
XXH_cpuid(1, 0, abcd);
|
||||
|
||||
/*
|
||||
* Test for SSE2. The check is redundant on x86_64, but it doesn't hurt.
|
||||
*/
|
||||
if (XXH_unlikely((abcd[3] & XXH_SSE2_CPUID_MASK) != XXH_SSE2_CPUID_MASK))
|
||||
return best;
|
||||
|
||||
XXH_debugPrint("SSE2 support detected.");
|
||||
|
||||
best = XXH_SSE2;
|
||||
#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
|
||||
/* Make sure we have enough leaves */
|
||||
if (XXH_unlikely(max_leaves < 7))
|
||||
return best;
|
||||
|
||||
/* Test for OSXSAVE and XGETBV */
|
||||
if ((abcd[2] & XXH_OSXSAVE_CPUID_MASK) != XXH_OSXSAVE_CPUID_MASK)
|
||||
return best;
|
||||
|
||||
/* CPUID check for AVX features */
|
||||
XXH_cpuid(7, 0, abcd);
|
||||
|
||||
xgetbv_val = XXH_xgetbv();
|
||||
#if XXH_DISPATCH_AVX2
|
||||
/* Validate that AVX2 is supported by the CPU */
|
||||
if ((abcd[1] & XXH_AVX2_CPUID_MASK) != XXH_AVX2_CPUID_MASK)
|
||||
return best;
|
||||
|
||||
/* Validate that the OS supports YMM registers */
|
||||
if ((xgetbv_val & XXH_AVX2_XGETBV_MASK) != XXH_AVX2_XGETBV_MASK) {
|
||||
XXH_debugPrint("AVX2 supported by the CPU, but not the OS.");
|
||||
return best;
|
||||
}
|
||||
|
||||
/* AVX2 supported */
|
||||
XXH_debugPrint("AVX2 support detected.");
|
||||
best = XXH_AVX2;
|
||||
#endif
|
||||
#if XXH_DISPATCH_AVX512
|
||||
/* Check if AVX512F is supported by the CPU */
|
||||
if ((abcd[1] & XXH_AVX512F_CPUID_MASK) != XXH_AVX512F_CPUID_MASK) {
|
||||
XXH_debugPrint("AVX512F not supported by CPU");
|
||||
return best;
|
||||
}
|
||||
|
||||
/* Validate that the OS supports ZMM registers */
|
||||
if ((xgetbv_val & XXH_AVX512F_XGETBV_MASK) != XXH_AVX512F_XGETBV_MASK) {
|
||||
XXH_debugPrint("AVX512F supported by the CPU, but not the OS.");
|
||||
return best;
|
||||
}
|
||||
|
||||
/* AVX512F supported */
|
||||
XXH_debugPrint("AVX512F support detected.");
|
||||
best = XXH_AVX512;
|
||||
#endif
|
||||
#endif
|
||||
return best;
|
||||
}
|
||||
|
||||
|
||||
/* === Vector implementations === */
|
||||
|
||||
/*! @cond PRIVATE */
|
||||
/*!
|
||||
* @private
|
||||
* @brief Defines the various dispatch functions.
|
||||
*
|
||||
* TODO: Consolidate?
|
||||
*
|
||||
* @param suffix The suffix for the functions, e.g. sse2 or scalar
|
||||
* @param target XXH_TARGET_* or empty.
|
||||
*/
|
||||
|
||||
#define XXH_DEFINE_DISPATCH_FUNCS(suffix, target) \
|
||||
\
|
||||
/* === XXH3, default variants === */ \
|
||||
\
|
||||
XXH_NO_INLINE target XXH64_hash_t \
|
||||
XXHL64_default_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input, \
|
||||
size_t len) \
|
||||
{ \
|
||||
return XXH3_hashLong_64b_internal( \
|
||||
input, len, XXH3_kSecret, sizeof(XXH3_kSecret), \
|
||||
XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix \
|
||||
); \
|
||||
} \
|
||||
\
|
||||
/* === XXH3, Seeded variants === */ \
|
||||
\
|
||||
XXH_NO_INLINE target XXH64_hash_t \
|
||||
XXHL64_seed_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input, size_t len, \
|
||||
XXH64_hash_t seed) \
|
||||
{ \
|
||||
return XXH3_hashLong_64b_withSeed_internal( \
|
||||
input, len, seed, XXH3_accumulate_##suffix, \
|
||||
XXH3_scrambleAcc_##suffix, XXH3_initCustomSecret_##suffix \
|
||||
); \
|
||||
} \
|
||||
\
|
||||
/* === XXH3, Secret variants === */ \
|
||||
\
|
||||
XXH_NO_INLINE target XXH64_hash_t \
|
||||
XXHL64_secret_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input, \
|
||||
size_t len, XXH_NOESCAPE const void* secret, \
|
||||
size_t secretLen) \
|
||||
{ \
|
||||
return XXH3_hashLong_64b_internal( \
|
||||
input, len, secret, secretLen, \
|
||||
XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix \
|
||||
); \
|
||||
} \
|
||||
\
|
||||
/* === XXH3 update variants === */ \
|
||||
\
|
||||
XXH_NO_INLINE target XXH_errorcode \
|
||||
XXH3_update_##suffix(XXH_NOESCAPE XXH3_state_t* state, \
|
||||
XXH_NOESCAPE const void* input, size_t len) \
|
||||
{ \
|
||||
return XXH3_update(state, (const xxh_u8*)input, len, \
|
||||
XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix); \
|
||||
} \
|
||||
\
|
||||
/* === XXH128 default variants === */ \
|
||||
\
|
||||
XXH_NO_INLINE target XXH128_hash_t \
|
||||
XXHL128_default_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input, \
|
||||
size_t len) \
|
||||
{ \
|
||||
return XXH3_hashLong_128b_internal( \
|
||||
input, len, XXH3_kSecret, sizeof(XXH3_kSecret), \
|
||||
XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix \
|
||||
); \
|
||||
} \
|
||||
\
|
||||
/* === XXH128 Secret variants === */ \
|
||||
\
|
||||
XXH_NO_INLINE target XXH128_hash_t \
|
||||
XXHL128_secret_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input, \
|
||||
size_t len, \
|
||||
XXH_NOESCAPE const void* XXH_RESTRICT secret, \
|
||||
size_t secretLen) \
|
||||
{ \
|
||||
return XXH3_hashLong_128b_internal( \
|
||||
input, len, (const xxh_u8*)secret, secretLen, \
|
||||
XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix); \
|
||||
} \
|
||||
\
|
||||
/* === XXH128 Seeded variants === */ \
|
||||
\
|
||||
XXH_NO_INLINE target XXH128_hash_t \
|
||||
XXHL128_seed_##suffix(XXH_NOESCAPE const void* XXH_RESTRICT input, size_t len,\
|
||||
XXH64_hash_t seed) \
|
||||
{ \
|
||||
return XXH3_hashLong_128b_withSeed_internal(input, len, seed, \
|
||||
XXH3_accumulate_##suffix, XXH3_scrambleAcc_##suffix, \
|
||||
XXH3_initCustomSecret_##suffix); \
|
||||
}
|
||||
|
||||
/*! @endcond */
|
||||
/* End XXH_DEFINE_DISPATCH_FUNCS */
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
#if XXH_DISPATCH_SCALAR
|
||||
XXH_DEFINE_DISPATCH_FUNCS(scalar, /* nothing */)
|
||||
#endif
|
||||
XXH_DEFINE_DISPATCH_FUNCS(sse2, XXH_TARGET_SSE2)
|
||||
#if XXH_DISPATCH_AVX2
|
||||
XXH_DEFINE_DISPATCH_FUNCS(avx2, XXH_TARGET_AVX2)
|
||||
#endif
|
||||
#if XXH_DISPATCH_AVX512
|
||||
XXH_DEFINE_DISPATCH_FUNCS(avx512, XXH_TARGET_AVX512)
|
||||
#endif
|
||||
#undef XXH_DEFINE_DISPATCH_FUNCS
|
||||
/*! @endcond */
|
||||
|
||||
/* ==== Dispatchers ==== */
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_default)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t);
|
||||
|
||||
typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSeed)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t, XXH64_hash_t);
|
||||
|
||||
typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSecret)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t, XXH_NOESCAPE const void* XXH_RESTRICT, size_t);
|
||||
|
||||
typedef XXH_errorcode (*XXH3_dispatchx86_update)(XXH_NOESCAPE XXH3_state_t*, XXH_NOESCAPE const void*, size_t);
|
||||
|
||||
typedef struct {
|
||||
XXH3_dispatchx86_hashLong64_default hashLong64_default;
|
||||
XXH3_dispatchx86_hashLong64_withSeed hashLong64_seed;
|
||||
XXH3_dispatchx86_hashLong64_withSecret hashLong64_secret;
|
||||
XXH3_dispatchx86_update update;
|
||||
} XXH_dispatchFunctions_s;
|
||||
|
||||
#define XXH_NB_DISPATCHES 4
|
||||
/*! @endcond */
|
||||
|
||||
/*!
|
||||
* @private
|
||||
* @brief Table of dispatchers for @ref XXH3_64bits().
|
||||
*
|
||||
* @pre The indices must match @ref XXH_VECTOR_TYPE.
|
||||
*/
|
||||
static const XXH_dispatchFunctions_s XXH_kDispatch[XXH_NB_DISPATCHES] = {
|
||||
#if XXH_DISPATCH_SCALAR
|
||||
/* Scalar */ { XXHL64_default_scalar, XXHL64_seed_scalar, XXHL64_secret_scalar, XXH3_update_scalar },
|
||||
#else
|
||||
/* Scalar */ { NULL, NULL, NULL, NULL },
|
||||
#endif
|
||||
/* SSE2 */ { XXHL64_default_sse2, XXHL64_seed_sse2, XXHL64_secret_sse2, XXH3_update_sse2 },
|
||||
#if XXH_DISPATCH_AVX2
|
||||
/* AVX2 */ { XXHL64_default_avx2, XXHL64_seed_avx2, XXHL64_secret_avx2, XXH3_update_avx2 },
|
||||
#else
|
||||
/* AVX2 */ { NULL, NULL, NULL, NULL },
|
||||
#endif
|
||||
#if XXH_DISPATCH_AVX512
|
||||
/* AVX512 */ { XXHL64_default_avx512, XXHL64_seed_avx512, XXHL64_secret_avx512, XXH3_update_avx512 }
|
||||
#else
|
||||
/* AVX512 */ { NULL, NULL, NULL, NULL }
|
||||
#endif
|
||||
};
|
||||
/*!
|
||||
* @private
|
||||
* @brief The selected dispatch table for @ref XXH3_64bits().
|
||||
*/
|
||||
static XXH_dispatchFunctions_s XXH_g_dispatch = { NULL, NULL, NULL, NULL };
|
||||
|
||||
|
||||
/*! @cond Doxygen ignores this part */
|
||||
typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_default)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t);
|
||||
|
||||
typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSeed)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t, XXH64_hash_t);
|
||||
|
||||
typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSecret)(XXH_NOESCAPE const void* XXH_RESTRICT, size_t, const void* XXH_RESTRICT, size_t);
|
||||
|
||||
typedef struct {
|
||||
XXH3_dispatchx86_hashLong128_default hashLong128_default;
|
||||
XXH3_dispatchx86_hashLong128_withSeed hashLong128_seed;
|
||||
XXH3_dispatchx86_hashLong128_withSecret hashLong128_secret;
|
||||
XXH3_dispatchx86_update update;
|
||||
} XXH_dispatch128Functions_s;
|
||||
/*! @endcond */
|
||||
|
||||
|
||||
/*!
|
||||
* @private
|
||||
* @brief Table of dispatchers for @ref XXH3_128bits().
|
||||
*
|
||||
* @pre The indices must match @ref XXH_VECTOR_TYPE.
|
||||
*/
|
||||
static const XXH_dispatch128Functions_s XXH_kDispatch128[XXH_NB_DISPATCHES] = {
|
||||
#if XXH_DISPATCH_SCALAR
|
||||
/* Scalar */ { XXHL128_default_scalar, XXHL128_seed_scalar, XXHL128_secret_scalar, XXH3_update_scalar },
|
||||
#else
|
||||
/* Scalar */ { NULL, NULL, NULL, NULL },
|
||||
#endif
|
||||
/* SSE2 */ { XXHL128_default_sse2, XXHL128_seed_sse2, XXHL128_secret_sse2, XXH3_update_sse2 },
|
||||
#if XXH_DISPATCH_AVX2
|
||||
/* AVX2 */ { XXHL128_default_avx2, XXHL128_seed_avx2, XXHL128_secret_avx2, XXH3_update_avx2 },
|
||||
#else
|
||||
/* AVX2 */ { NULL, NULL, NULL, NULL },
|
||||
#endif
|
||||
#if XXH_DISPATCH_AVX512
|
||||
/* AVX512 */ { XXHL128_default_avx512, XXHL128_seed_avx512, XXHL128_secret_avx512, XXH3_update_avx512 }
|
||||
#else
|
||||
/* AVX512 */ { NULL, NULL, NULL, NULL }
|
||||
#endif
|
||||
};
|
||||
|
||||
/*!
|
||||
* @private
|
||||
* @brief The selected dispatch table for @ref XXH3_64bits().
|
||||
*/
|
||||
static XXH_dispatch128Functions_s XXH_g_dispatch128 = { NULL, NULL, NULL, NULL };
|
||||
|
||||
/*!
|
||||
* @private
|
||||
* @brief Runs a CPUID check and sets the correct dispatch tables.
|
||||
*/
|
||||
static XXH_CONSTRUCTOR void XXH_setDispatch(void)
|
||||
{
|
||||
int vecID = XXH_featureTest();
|
||||
XXH_STATIC_ASSERT(XXH_AVX512 == XXH_NB_DISPATCHES-1);
|
||||
assert(XXH_SCALAR <= vecID && vecID <= XXH_AVX512);
|
||||
#if !XXH_DISPATCH_SCALAR
|
||||
assert(vecID != XXH_SCALAR);
|
||||
#endif
|
||||
#if !XXH_DISPATCH_AVX512
|
||||
assert(vecID != XXH_AVX512);
|
||||
#endif
|
||||
#if !XXH_DISPATCH_AVX2
|
||||
assert(vecID != XXH_AVX2);
|
||||
#endif
|
||||
XXH_g_dispatch = XXH_kDispatch[vecID];
|
||||
XXH_g_dispatch128 = XXH_kDispatch128[vecID];
|
||||
}
|
||||
|
||||
|
||||
/* ==== XXH3 public functions ==== */
|
||||
/*! @cond Doxygen ignores this part */
|
||||
|
||||
static XXH64_hash_t
|
||||
XXH3_hashLong_64b_defaultSecret_selection(const void* input, size_t len,
|
||||
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
|
||||
{
|
||||
(void)seed64; (void)secret; (void)secretLen;
|
||||
if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch.hashLong64_default == NULL)
|
||||
XXH_setDispatch();
|
||||
return XXH_g_dispatch.hashLong64_default(input, len);
|
||||
}
|
||||
|
||||
XXH64_hash_t XXH3_64bits_dispatch(XXH_NOESCAPE const void* input, size_t len)
|
||||
{
|
||||
return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_defaultSecret_selection);
|
||||
}
|
||||
|
||||
static XXH64_hash_t
|
||||
XXH3_hashLong_64b_withSeed_selection(const void* input, size_t len,
|
||||
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
|
||||
{
|
||||
(void)secret; (void)secretLen;
|
||||
if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch.hashLong64_seed == NULL)
|
||||
XXH_setDispatch();
|
||||
return XXH_g_dispatch.hashLong64_seed(input, len, seed64);
|
||||
}
|
||||
|
||||
XXH64_hash_t XXH3_64bits_withSeed_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
|
||||
{
|
||||
return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed_selection);
|
||||
}
|
||||
|
||||
static XXH64_hash_t
|
||||
XXH3_hashLong_64b_withSecret_selection(const void* input, size_t len,
|
||||
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
|
||||
{
|
||||
(void)seed64;
|
||||
if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch.hashLong64_secret == NULL)
|
||||
XXH_setDispatch();
|
||||
return XXH_g_dispatch.hashLong64_secret(input, len, secret, secretLen);
|
||||
}
|
||||
|
||||
XXH64_hash_t XXH3_64bits_withSecret_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretLen)
|
||||
{
|
||||
return XXH3_64bits_internal(input, len, 0, secret, secretLen, XXH3_hashLong_64b_withSecret_selection);
|
||||
}
|
||||
|
||||
XXH_errorcode
|
||||
XXH3_64bits_update_dispatch(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len)
|
||||
{
|
||||
if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch.update == NULL)
|
||||
XXH_setDispatch();
|
||||
|
||||
return XXH_g_dispatch.update(state, (const xxh_u8*)input, len);
|
||||
}
|
||||
|
||||
/*! @endcond */
|
||||
|
||||
|
||||
/* ==== XXH128 public functions ==== */
|
||||
/*! @cond Doxygen ignores this part */
|
||||
|
||||
static XXH128_hash_t
|
||||
XXH3_hashLong_128b_defaultSecret_selection(const void* input, size_t len,
|
||||
XXH64_hash_t seed64, const void* secret, size_t secretLen)
|
||||
{
|
||||
(void)seed64; (void)secret; (void)secretLen;
|
||||
if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch128.hashLong128_default == NULL)
|
||||
XXH_setDispatch();
|
||||
return XXH_g_dispatch128.hashLong128_default(input, len);
|
||||
}
|
||||
|
||||
XXH128_hash_t XXH3_128bits_dispatch(XXH_NOESCAPE const void* input, size_t len)
|
||||
{
|
||||
return XXH3_128bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_defaultSecret_selection);
|
||||
}
|
||||
|
||||
static XXH128_hash_t
|
||||
XXH3_hashLong_128b_withSeed_selection(const void* input, size_t len,
|
||||
XXH64_hash_t seed64, const void* secret, size_t secretLen)
|
||||
{
|
||||
(void)secret; (void)secretLen;
|
||||
if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch128.hashLong128_seed == NULL)
|
||||
XXH_setDispatch();
|
||||
return XXH_g_dispatch128.hashLong128_seed(input, len, seed64);
|
||||
}
|
||||
|
||||
XXH128_hash_t XXH3_128bits_withSeed_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
|
||||
{
|
||||
return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_withSeed_selection);
|
||||
}
|
||||
|
||||
static XXH128_hash_t
|
||||
XXH3_hashLong_128b_withSecret_selection(const void* input, size_t len,
|
||||
XXH64_hash_t seed64, const void* secret, size_t secretLen)
|
||||
{
|
||||
(void)seed64;
|
||||
if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch128.hashLong128_secret == NULL)
|
||||
XXH_setDispatch();
|
||||
return XXH_g_dispatch128.hashLong128_secret(input, len, secret, secretLen);
|
||||
}
|
||||
|
||||
XXH128_hash_t XXH3_128bits_withSecret_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretLen)
|
||||
{
|
||||
return XXH3_128bits_internal(input, len, 0, secret, secretLen, XXH3_hashLong_128b_withSecret_selection);
|
||||
}
|
||||
|
||||
XXH_errorcode
|
||||
XXH3_128bits_update_dispatch(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len)
|
||||
{
|
||||
if (XXH_DISPATCH_MAYBE_NULL && XXH_g_dispatch128.update == NULL)
|
||||
XXH_setDispatch();
|
||||
return XXH_g_dispatch128.update(state, (const xxh_u8*)input, len);
|
||||
}
|
||||
|
||||
/*! @endcond */
|
||||
|
||||
#if defined (__cplusplus)
|
||||
}
|
||||
#endif
|
||||
/*! @} */
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* xxHash - XXH3 Dispatcher for x86-based targets
|
||||
* Copyright (C) 2020-2021 Yann Collet
|
||||
*
|
||||
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* You can contact the author at:
|
||||
* - xxHash homepage: https://www.xxhash.com
|
||||
* - xxHash source repository: https://github.com/Cyan4973/xxHash
|
||||
*/
|
||||
|
||||
#ifndef XXH_X86DISPATCH_H_13563687684
|
||||
#define XXH_X86DISPATCH_H_13563687684
|
||||
|
||||
#include "xxhash.h" /* XXH64_hash_t, XXH3_state_t */
|
||||
|
||||
#if defined (__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_dispatch(XXH_NOESCAPE const void* input, size_t len);
|
||||
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed);
|
||||
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretLen);
|
||||
XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update_dispatch(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len);
|
||||
|
||||
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_dispatch(XXH_NOESCAPE const void* input, size_t len);
|
||||
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed);
|
||||
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret_dispatch(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretLen);
|
||||
XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update_dispatch(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len);
|
||||
|
||||
#if defined (__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* automatic replacement of XXH3 functions.
|
||||
* can be disabled by setting XXH_DISPATCH_DISABLE_REPLACE */
|
||||
#ifndef XXH_DISPATCH_DISABLE_REPLACE
|
||||
|
||||
# undef XXH3_64bits
|
||||
# define XXH3_64bits XXH3_64bits_dispatch
|
||||
# undef XXH3_64bits_withSeed
|
||||
# define XXH3_64bits_withSeed XXH3_64bits_withSeed_dispatch
|
||||
# undef XXH3_64bits_withSecret
|
||||
# define XXH3_64bits_withSecret XXH3_64bits_withSecret_dispatch
|
||||
# undef XXH3_64bits_update
|
||||
# define XXH3_64bits_update XXH3_64bits_update_dispatch
|
||||
|
||||
# undef XXH128
|
||||
# define XXH128 XXH3_128bits_withSeed_dispatch
|
||||
# undef XXH3_128bits
|
||||
# define XXH3_128bits XXH3_128bits_dispatch
|
||||
# undef XXH3_128bits_withSeed
|
||||
# define XXH3_128bits_withSeed XXH3_128bits_withSeed_dispatch
|
||||
# undef XXH3_128bits_withSecret
|
||||
# define XXH3_128bits_withSecret XXH3_128bits_withSecret_dispatch
|
||||
# undef XXH3_128bits_update
|
||||
# define XXH3_128bits_update XXH3_128bits_update_dispatch
|
||||
|
||||
#endif /* XXH_DISPATCH_DISABLE_REPLACE */
|
||||
|
||||
#endif /* XXH_X86DISPATCH_H_13563687684 */
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* xxHash - Extremely Fast Hash algorithm
|
||||
* Copyright (C) 2012-2021 Yann Collet
|
||||
*
|
||||
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* You can contact the author at:
|
||||
* - xxHash homepage: https://www.xxhash.com
|
||||
* - xxHash source repository: https://github.com/Cyan4973/xxHash
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* xxhash.c instantiates functions defined in xxhash.h
|
||||
*/
|
||||
|
||||
#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */
|
||||
#define XXH_IMPLEMENTATION /* access definitions */
|
||||
|
||||
#include "xxhash.h"
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user