Skip to content

Commit d886d75

Browse files
committed
stdlib: implement SipHash-1-3 and SipHash-2-4
1 parent c022c6d commit d886d75

File tree

4 files changed

+503
-0
lines changed

4 files changed

+503
-0
lines changed

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ set(SWIFTLIB_ESSENTIAL
101101
REPL.swift
102102
Reverse.swift
103103
Runtime.swift.gyb
104+
SipHash.swift.gyb
104105
Sequence.swift
105106
SequenceAlgorithms.swift.gyb
106107
SequenceWrapper.swift

stdlib/public/core/GroupInfo.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
"AnyHashable.swift",
136136
"Interval.swift",
137137
"Hashing.swift",
138+
"SipHash.swift",
138139
"ErrorType.swift",
139140
"InputStream.swift",
140141
"LifetimeManager.swift",

stdlib/public/core/SipHash.swift.gyb

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
/// This file implements SipHash-2-4 and SipHash-1-3
13+
/// (https://131002.net/siphash/).
14+
///
15+
/// This file is based on the reference C implementation, which was released
16+
/// to public domain by:
17+
///
18+
/// * Jean-Philippe Aumasson <[email protected]>
19+
/// * Daniel J. Bernstein <[email protected]>
20+
//===----------------------------------------------------------------------===//
21+
22+
internal enum _SipHashDetail {
23+
@inline(__always)
24+
internal static func _rotate(_ x: UInt64, leftBy amount: Int) -> UInt64 {
25+
return (x << UInt64(amount)) | (x >> (64 - UInt64(amount)))
26+
}
27+
28+
@inline(__always)
29+
internal static func _loadUnalignedUInt64LE(
30+
from p: UnsafeRawPointer
31+
) -> UInt64 {
32+
return
33+
UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) |
34+
(UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) << 8) |
35+
(UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) << 16) |
36+
(UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) << 24) |
37+
(UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) << 32) |
38+
(UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) << 40) |
39+
(UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) << 48) |
40+
(UInt64(p.load(fromByteOffset: 7, as: UInt8.self)) << 56)
41+
}
42+
43+
@inline(__always)
44+
internal static func _loadPartialUnalignedUInt64LE(
45+
from p: UnsafeRawPointer,
46+
byteCount: Int
47+
) -> UInt64 {
48+
_sanityCheck((0..<8).contains(byteCount))
49+
var result: UInt64 = 0
50+
if byteCount >= 1 { result |= UInt64(p.load(fromByteOffset: 0, as: UInt8.self)) }
51+
if byteCount >= 2 { result |= UInt64(p.load(fromByteOffset: 1, as: UInt8.self)) << 8 }
52+
if byteCount >= 3 { result |= UInt64(p.load(fromByteOffset: 2, as: UInt8.self)) << 16 }
53+
if byteCount >= 4 { result |= UInt64(p.load(fromByteOffset: 3, as: UInt8.self)) << 24 }
54+
if byteCount >= 5 { result |= UInt64(p.load(fromByteOffset: 4, as: UInt8.self)) << 32 }
55+
if byteCount >= 6 { result |= UInt64(p.load(fromByteOffset: 5, as: UInt8.self)) << 40 }
56+
if byteCount >= 7 { result |= UInt64(p.load(fromByteOffset: 6, as: UInt8.self)) << 48 }
57+
return result
58+
}
59+
60+
@inline(__always)
61+
internal static func _sipRound(
62+
v0: inout UInt64,
63+
v1: inout UInt64,
64+
v2: inout UInt64,
65+
v3: inout UInt64
66+
) {
67+
v0 = v0 &+ v1
68+
v1 = _rotate(v1, leftBy: 13)
69+
v1 ^= v0
70+
v0 = _rotate(v0, leftBy: 32)
71+
v2 = v2 &+ v3
72+
v3 = _rotate(v3, leftBy: 16)
73+
v3 ^= v2
74+
v0 = v0 &+ v3
75+
v3 = _rotate(v3, leftBy: 21)
76+
v3 ^= v0
77+
v2 = v2 &+ v1
78+
v1 = _rotate(v1, leftBy: 17)
79+
v1 ^= v2
80+
v2 = _rotate(v2, leftBy: 32)
81+
}
82+
}
83+
84+
% for (c_rounds, d_rounds) in [(2, 4), (1, 3)]:
85+
% Self = '_SipHash{}{}Context'.format(c_rounds, d_rounds)
86+
87+
public // @testable
88+
struct ${Self} {
89+
// "somepseudorandomlygeneratedbytes"
90+
internal var v0: UInt64 = 0x736f6d6570736575
91+
internal var v1: UInt64 = 0x646f72616e646f6d
92+
internal var v2: UInt64 = 0x6c7967656e657261
93+
internal var v3: UInt64 = 0x7465646279746573
94+
95+
internal var hashedByteCount: UInt64 = 0
96+
97+
internal var dataTail: UInt64 = 0
98+
internal var dataTailByteCount: Int = 0
99+
100+
internal var finalizedHash: UInt64? = nil
101+
102+
public init(key: (UInt64, UInt64)) {
103+
v3 ^= key.1
104+
v2 ^= key.0
105+
v1 ^= key.1
106+
v0 ^= key.0
107+
}
108+
109+
// FIXME(ABI): Use UnsafeRawBufferPointer.
110+
public // @testable
111+
mutating func append(_ data: UnsafeRawPointer, byteCount: Int) {
112+
_append_alwaysInline(data, byteCount: byteCount)
113+
}
114+
115+
// FIXME(ABI): Use UnsafeRawBufferPointer.
116+
@inline(__always)
117+
internal mutating func _append_alwaysInline(
118+
_ data: UnsafeRawPointer,
119+
byteCount: Int
120+
) {
121+
precondition(finalizedHash == nil)
122+
_sanityCheck((0..<8).contains(dataTailByteCount))
123+
124+
let dataEnd = data + byteCount
125+
126+
var data = data
127+
var byteCount = byteCount
128+
if dataTailByteCount != 0 {
129+
let restByteCount = min(
130+
MemoryLayout<UInt64>.size - dataTailByteCount,
131+
byteCount)
132+
let rest = _SipHashDetail._loadPartialUnalignedUInt64LE(
133+
from: data,
134+
byteCount: restByteCount)
135+
dataTail |= rest << UInt64(dataTailByteCount * 8)
136+
dataTailByteCount += restByteCount
137+
data += restByteCount
138+
byteCount -= restByteCount
139+
}
140+
141+
if dataTailByteCount == MemoryLayout<UInt64>.size {
142+
_appendDirectly(dataTail)
143+
dataTail = 0
144+
dataTailByteCount = 0
145+
} else if dataTailByteCount != 0 {
146+
_sanityCheck(data == dataEnd)
147+
return
148+
}
149+
150+
let endOfWords =
151+
data + byteCount - (byteCount % MemoryLayout<UInt64>.size)
152+
while data != endOfWords {
153+
_appendDirectly(_SipHashDetail._loadUnalignedUInt64LE(from: data))
154+
data += 8
155+
// No need to update `byteCount`, it is not used beyond this point.
156+
}
157+
158+
if data != dataEnd {
159+
dataTailByteCount = dataEnd - data
160+
dataTail = _SipHashDetail._loadPartialUnalignedUInt64LE(
161+
from: data,
162+
byteCount: dataTailByteCount)
163+
}
164+
}
165+
166+
/// This function mixes in the given word directly into the state,
167+
/// ignoring `dataTail`.
168+
@inline(__always)
169+
internal mutating func _appendDirectly(_ m: UInt64) {
170+
v3 ^= m
171+
for _ in 0..<${c_rounds} {
172+
_SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3)
173+
}
174+
v0 ^= m
175+
hashedByteCount += 8
176+
}
177+
178+
public // @testable
179+
mutating func finalizeAndReturnHash() -> UInt64 {
180+
return _finalizeAndReturnHash_alwaysInline()
181+
}
182+
183+
@inline(__always)
184+
internal mutating func _finalizeAndReturnHash_alwaysInline() -> UInt64 {
185+
if let finalizedHash = finalizedHash {
186+
return finalizedHash
187+
}
188+
189+
_sanityCheck((0..<8).contains(dataTailByteCount))
190+
191+
hashedByteCount += UInt64(dataTailByteCount)
192+
let b: UInt64 = (hashedByteCount << 56) | dataTail
193+
194+
v3 ^= b
195+
for _ in 0..<${c_rounds} {
196+
_SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3)
197+
}
198+
v0 ^= b
199+
200+
v2 ^= 0xff
201+
202+
for _ in 0..<${d_rounds} {
203+
_SipHashDetail._sipRound(v0: &v0, v1: &v1, v2: &v2, v3: &v3)
204+
}
205+
206+
finalizedHash = v0 ^ v1 ^ v2 ^ v3
207+
return finalizedHash!
208+
}
209+
210+
// FIXME(ABI): Use UnsafeRawBufferPointer.
211+
public // @testable
212+
static func hash(
213+
data: UnsafeRawPointer,
214+
dataByteCount: Int,
215+
key: (UInt64, UInt64)
216+
) -> UInt64 {
217+
return ${Self}._hash_alwaysInline(
218+
data: data,
219+
dataByteCount: dataByteCount,
220+
key: key)
221+
}
222+
223+
// FIXME(ABI): Use UnsafeRawBufferPointer.
224+
@inline(__always)
225+
public // @testable
226+
static func _hash_alwaysInline(
227+
data: UnsafeRawPointer,
228+
dataByteCount: Int,
229+
key: (UInt64, UInt64)
230+
) -> UInt64 {
231+
var context = ${Self}(key: key)
232+
context._append_alwaysInline(data, byteCount: dataByteCount)
233+
return context._finalizeAndReturnHash_alwaysInline()
234+
}
235+
}
236+
% end

0 commit comments

Comments
 (0)