Add _stdlib_random for more platforms (#1)

* Remove refs to Countable ranges

* Add `_stdlib_random` for more platforms

* Use `getrandom` (if available) for Android, Cygwin

* Reorder the `_stdlib_random` functions

* Also include <features.h> on Linux

* Add `#error TODO` in `_stdlib_random` for Windows

* Colon after Fatal Error

Performance improvement for Random

gybify ranges

Fix typo in 'basic random numbers'
Add _stdlib_random as a testable method

Switch to generic constraints

Hopefully link against bcrypt

Fix some implementation details

1. Uniform distribution is now uniform
2. Apply Jens' method for uniform floats

Fix a lineable attribute
This commit is contained in:
Ben Rimmington
2018-02-10 00:09:14 +00:00
committed by Azoy
parent d23d219e95
commit a5df0ef83d
11 changed files with 342 additions and 183 deletions

View File

@@ -92,12 +92,24 @@ public struct Bool {
/// - Parameter generator: The random number generator to use when getting a
/// random Boolean.
/// - Returns: A random Boolean.
@_inlineable
public static func random(
using generator: RandomNumberGenerator = Random.default
@inlinable
public static func random<T: RandomNumberGenerator>(
using generator: T
) -> Bool {
return generator.next() % 2 == 0
}
/// Returns a random Boolean
///
/// - Parameter generator: The random number generator to use when getting a
/// random Boolean.
/// - Returns: A random Boolean.
///
/// This uses the standard library's default random number generator.
@inlinable
public static func random() -> Bool {
return Bool.random(using: Random.default)
}
}
extension Bool : _ExpressibleByBuiltinBooleanLiteral, ExpressibleByBooleanLiteral {

View File

@@ -229,6 +229,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
${EXECINFO_LIBRARY})
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
list(APPEND swift_core_link_flags "$ENV{SystemRoot}/system32/bcrypt.dll")
endif()
option(SWIFT_CHECK_ESSENTIAL_STDLIB
"Check core standard library layering by linking its essential subset"
FALSE)

View File

@@ -480,57 +480,6 @@ extension ClosedRange where Bound: Strideable, Bound.Stride : SignedInteger {
}
}
extension ClosedRange
where Bound : FixedWidthInteger,
Bound.Magnitude : UnsignedInteger {
/// Returns a random element from this collection.
///
/// - Parameter generator: The random number generator to use when getting
/// a random element.
/// - Returns: A random element from this collection.
///
/// A good example of this is getting a random greeting from an array:
///
/// let greetings = ["hi", "hey", "hello", "hola"]
/// let randomGreeting = greetings.random()
///
/// If the collection is empty, the value of this function is `nil`.
///
/// let numbers = [10, 20, 30, 40, 50]
/// if let randomNumber = numbers.random() {
/// print(randomNumber)
/// }
/// // Could print "20", perhaps
@_inlineable
public func random(
using generator: RandomNumberGenerator = Random.default
) -> Element? {
let isLowerNegative = Bound.isSigned && lowerBound < 0
let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0)
var delta: Bound.Magnitude
if isLowerNegative {
delta = sameSign
? lowerBound.magnitude - upperBound.magnitude
: lowerBound.magnitude + upperBound.magnitude
} else {
delta = upperBound.magnitude - lowerBound.magnitude
}
if delta == Bound.Magnitude.max {
return Bound(truncatingIfNeeded: generator.next() as Bound.Magnitude)
}
delta += 1
let randomMagnitude = generator.next(upperBound: delta)
if sameSign {
return lowerBound + Bound(randomMagnitude)
} else {
return Bound.isSigned && randomMagnitude <= upperBound.magnitude
? Bound(randomMagnitude)
: 0 - Bound(randomMagnitude - upperBound.magnitude)
}
}
}
extension ClosedRange {
@inlinable
public func overlaps(_ other: ClosedRange<Bound>) -> Bool {

View File

@@ -1034,12 +1034,12 @@ extension Collection {
/// print(randomNumber)
/// }
/// // Could print "20", perhaps
@_inlineable
public func random(
using generator: RandomNumberGenerator = Random.default
@inlinable
public func random<T: RandomNumberGenerator>(
using generator: T
) -> Element? {
guard !isEmpty else { return nil }
let random = generator.next(upperBound: UInt(self.count))
let random = generator.next(upperBound: UInt(count))
let index = self.index(
self.startIndex,
offsetBy: IndexDistance(random),
@@ -1048,6 +1048,31 @@ extension Collection {
return self[index]
}
/// Returns a random element from this collection.
///
/// - Parameter generator: The random number generator to use when getting
/// a random element.
/// - Returns: A random element from this collection.
///
/// A good example of this is getting a random greeting from an array:
///
/// let greetings = ["hi", "hey", "hello", "hola"]
/// let randomGreeting = greetings.random()
///
/// If the collection is empty, the value of this function is `nil`.
///
/// let numbers = [10, 20, 30, 40, 50]
/// if let randomNumber = numbers.random() {
/// print(randomNumber)
/// }
/// // Could print "20", perhaps
///
/// This uses the standard library's default random number generator.
@inlinable
public func random() -> Element? {
return random(using: Random.default)
}
/// Do not use this method directly; call advanced(by: n) instead.
@inlinable
@inline(__always)

View File

@@ -368,7 +368,7 @@ extension MutableCollection where Self : BidirectionalCollection {
}
//===----------------------------------------------------------------------===//
// shuffled()
// shuffled()/shuffle()
//===----------------------------------------------------------------------===//
extension Sequence {
@@ -377,14 +377,26 @@ extension Sequence {
/// - Parameter generator: The random number generator to use when shuffling
/// the sequence.
/// - Returns: A shuffled array of this sequence's elements.
@_inlineable
public func shuffled(
using generator: RandomNumberGenerator = Random.default
@inlinable
public func shuffled<T: RandomNumberGenerator>(
using generator: T
) -> [Element] {
var result = ContiguousArray(self)
result.shuffle(using: generator)
return Array(result)
}
/// Returns the elements of the sequence, shuffled.
///
/// - Parameter generator: The random number generator to use when shuffling
/// the sequence.
/// - Returns: A shuffled array of this sequence's elements.
///
/// This uses the standard library's default random number generator.
@inlinable
public func shuffled() -> [Element] {
return shuffled(using: Random.default)
}
}
extension MutableCollection {
@@ -392,9 +404,9 @@ extension MutableCollection {
///
/// - Parameter generator: The random number generator to use when shuffling
/// the collection.
@_inlineable
public mutating func shuffle(
using generator: RandomNumberGenerator = Random.default
@inlinable
public mutating func shuffle<T: RandomNumberGenerator>(
using generator: T
) {
guard count > 1 else { return }
var amount = count
@@ -409,6 +421,17 @@ extension MutableCollection {
formIndex(after: &currentIndex)
}
}
/// Shuffles the collection in place.
///
/// - Parameter generator: The random number generator to use when shuffling
/// the collection.
///
/// This uses the standard library's default random number generator.
@inlinable
public mutating func shuffle() {
shuffle(using: Random.default)
}
}
//===----------------------------------------------------------------------===//

View File

@@ -2413,10 +2413,10 @@ where Self.RawSignificand : FixedWidthInteger,
/// - Parameter generator: The random number generator to use when getting
/// the random floating point.
/// - Returns: A random representation of this floating point.
@_inlineable
public static func random(
@inlinable
public static func random<T: RandomNumberGenerator>(
in range: ${Range}<Self>,
using generator: RandomNumberGenerator = Random.default
using generator: T
) -> Self {
% if 'Closed' not in Range:
_precondition(
@@ -2425,7 +2425,7 @@ where Self.RawSignificand : FixedWidthInteger,
)
% end
let delta = range.upperBound - range.lowerBound
let maxSignificand: Self.RawSignificand = 1 << Self.significandBitCount
let maxSignificand = Self.RawSignificand(1 << (Self.significandBitCount + 1))
% if 'Closed' not in Range:
let rand: Self.RawSignificand = generator.next(upperBound: maxSignificand)
% else:
@@ -2434,14 +2434,23 @@ where Self.RawSignificand : FixedWidthInteger,
return range.upperBound
}
% end
let unitRandom = Self.init(
sign: .plus,
exponentBitPattern: (1 as Self).exponentBitPattern,
significandBitPattern: rand
) - 1
let unitRandom = Self.init(rand) * Self.ulpOfOne / 2
return delta * unitRandom + range.lowerBound
}
/// Returns a random representation of this floating point within the range.
///
/// - Parameter range: A ${Range} to determine the bounds to get a random value
/// from.
/// - Parameter generator: The random number generator to use when getting
/// the random floating point.
/// - Returns: A random representation of this floating point.
///
/// This uses the standard library's default random number generator.
@inlinable
public static func random(in range: ${Range}<Self>) -> Self {
return Self.random(in: range, using: Random.default)
}
}
% end

View File

@@ -2532,6 +2532,99 @@ ${assignmentOperatorComment(x.operator, False)}
% for Range in ['Range', 'ClosedRange']:
extension ${Range}
where Bound: FixedWidthInteger,
Bound.Magnitude: UnsignedInteger {
/// Returns a random element from this collection.
///
/// - Parameter generator: The random number generator to use when getting
/// a random element.
/// - Returns: A random element from this collection.
///
/// A good example of this is getting a random greeting from an array:
///
/// let greetings = ["hi", "hey", "hello", "hola"]
/// let randomGreeting = greetings.random()
///
/// If the collection is empty, the value of this function is `nil`.
///
/// let numbers = [10, 20, 30, 40, 50]
/// if let randomNumber = numbers.random() {
/// print(randomNumber)
/// }
/// // Could print "20", perhaps
@inlinable
public func random<T: RandomNumberGenerator>(
using generator: T
) -> Element? {
% if 'Closed' not in Range:
guard lowerBound != upperBound else {
return nil
}
% end
let isLowerNegative = Bound.isSigned && lowerBound < 0
let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0)
% if 'Closed' not in Range:
let delta: Bound.Magnitude
% else:
var delta: Bound.Magnitude
% end
if isLowerNegative {
delta = sameSign
? lowerBound.magnitude - upperBound.magnitude
: lowerBound.magnitude + upperBound.magnitude
} else {
delta = upperBound.magnitude - lowerBound.magnitude
}
% if 'Closed' in Range:
if delta == Bound.Magnitude.max {
return Bound(truncatingIfNeeded: generator.next() as Bound.Magnitude)
}
delta += 1
% end
let randomMagnitude = generator.next(upperBound: delta)
if sameSign {
return lowerBound + Bound(randomMagnitude)
} else {
% if 'Closed' not in Range:
return randomMagnitude < upperBound.magnitude
? Bound(randomMagnitude)
: -1 - Bound(randomMagnitude - upperBound.magnitude)
% else:
return Bound.isSigned && randomMagnitude <= upperBound.magnitude
? Bound(randomMagnitude)
: 0 - Bound(randomMagnitude - upperBound.magnitude)
% end
}
}
/// Returns a random element from this collection.
///
/// - Parameter generator: The random number generator to use when getting
/// a random element.
/// - Returns: A random element from this collection.
///
/// A good example of this is getting a random greeting from an array:
///
/// let greetings = ["hi", "hey", "hello", "hola"]
/// let randomGreeting = greetings.random()
///
/// If the collection is empty, the value of this function is `nil`.
///
/// let numbers = [10, 20, 30, 40, 50]
/// if let randomNumber = numbers.random() {
/// print(randomNumber)
/// }
/// // Could print "20", perhaps
///
/// This uses the standard library's default random number generator.
@inlinable
public func random() -> Element? {
return self.random(using: Random.default)
}
}
extension FixedWidthInteger
where Self.Stride : SignedInteger,
Self.Magnitude : UnsignedInteger {
@@ -2543,10 +2636,10 @@ where Self.Stride : SignedInteger,
/// - Parameter generator: The random number generator to use when getting
/// the random integer.
/// - Returns: A random representation of this integer.
@_inlineable
public static func random(
@inlinable
public static func random<T: RandomNumberGenerator>(
in range: ${Range}<Self>,
using generator: RandomNumberGenerator = Random.default
using generator: T
) -> Self {
% if 'Closed' not in Range:
_precondition(
@@ -2556,6 +2649,20 @@ where Self.Stride : SignedInteger,
% end
return range.random(using: generator)!
}
/// Returns a random representation of this integer within the range.
///
/// - Parameter range: A ${Range} to determine the bounds to get a random value
/// from.
/// - Parameter generator: The random number generator to use when getting
/// the random integer.
/// - Returns: A random representation of this integer.
///
/// This uses the standard library's default random number generator.
@inlinable
public static func random(in range: ${Range}<Self>) -> Self {
return Self.random(in: range, using: Random.default)
}
}
% end

View File

@@ -46,10 +46,10 @@ extension RandomNumberGenerator {
///
/// This differs from next() as this function has the ability to transform the
/// generated number to any unsigned integer.
@_inlineable
@inlinable
public func next<T: FixedWidthInteger & UnsignedInteger>() -> T {
if T.bitWidth == UInt64.bitWidth {
return T(self.next())
if T.bitWidth <= UInt64.bitWidth {
return T(truncatingIfNeeded: self.next())
}
let (quotient, remainder) = T.bitWidth.quotientAndRemainder(
@@ -76,11 +76,12 @@ extension RandomNumberGenerator {
/// - Parameter upperBound: The max number this can generate up to.
/// - Returns: A number that was randomly generated from 0 to upperBound
///
/// This uses the uniform distribution to form a random number within the
/// upperBound.
@_inlineable
/// By default, this uses the uniform distribution to form a random number
/// that is less than the upperBound.
@inlinable
public func next<T: FixedWidthInteger & UnsignedInteger>(upperBound: T) -> T {
let range = T.max % upperBound
let tmp = (T.max % upperBound) + 1
let range = tmp == upperBound ? 0 : tmp
var random: T = 0
repeat {
@@ -125,4 +126,23 @@ public struct Random : RandomNumberGenerator {
_stdlib_random(&random, MemoryLayout<UInt64>.size)
return random
}
/// Produces the next randomly generated number
///
/// - Returns: A number that was randomly generated
///
/// This differs from next() as this function has the ability to transform the
/// generated number to any unsigned integer.
public func next<T: FixedWidthInteger & UnsignedInteger>() -> T {
var random: T = 0
_stdlib_random(&random, MemoryLayout<T>.size)
return random
}
}
public // @testable
func _stdlib_random(_ bytes: UnsafeMutableRawBufferPointer) {
if !bytes.isEmpty {
_stdlib_random(bytes.baseAddress!, bytes.count)
}
}

View File

@@ -303,56 +303,6 @@ extension Range where Bound: Strideable, Bound.Stride : SignedInteger {
}
}
extension Range
where Bound : FixedWidthInteger,
Bound.Magnitude : UnsignedInteger {
/// Returns a random element from this collection.
///
/// - Parameter generator: The random number generator to use when getting
/// a random element.
/// - Returns: A random element from this collection.
///
/// A good example of this is getting a random greeting from an array:
///
/// let greetings = ["hi", "hey", "hello", "hola"]
/// let randomGreeting = greetings.random()
///
/// If the collection is empty, the value of this function is `nil`.
///
/// let numbers = [10, 20, 30, 40, 50]
/// if let randomNumber = numbers.random() {
/// print(randomNumber)
/// }
/// // Could print "20", perhaps
@_inlineable
public func random(
using generator: RandomNumberGenerator = Random.default
) -> Element? {
guard lowerBound != upperBound else {
return nil
}
let isLowerNegative = Bound.isSigned && lowerBound < 0
let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0)
let delta: Bound.Magnitude
if isLowerNegative {
delta = sameSign
? lowerBound.magnitude - upperBound.magnitude
: lowerBound.magnitude + upperBound.magnitude
} else {
delta = upperBound.magnitude - lowerBound.magnitude
}
let randomMagnitude = generator.next(upperBound: delta)
if sameSign {
return lowerBound + Bound(randomMagnitude)
} else {
return randomMagnitude < upperBound.magnitude
? Bound(randomMagnitude)
: -1 - Bound(randomMagnitude - upperBound.magnitude)
}
}
}
extension Range: RangeExpression {
/// Returns the range of indices described by this range expression within
/// the given collection.

View File

@@ -11,35 +11,67 @@
//===----------------------------------------------------------------------===//
#if defined(__APPLE__)
#define _REENTRANT
#include <math.h>
#endif
#include <random>
#include <type_traits>
#include <cmath>
#if defined(_WIN32)
#include <io.h>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/ioctl.h>
# define _REENTRANT
# include <math.h>
# include <Security/Security.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#if defined(__CYGWIN__)
# include <cygwin/version.h>
# if (CYGWIN_VERSION_API_MAJOR > 0) || (CYGWIN_VERSION_API_MINOR >= 306)
# include <sys/random.h>
# define SWIFT_STDLIB_USING_GETRANDOM
# endif
#endif
#if defined(__Fuchsia__)
# include <sys/random.h>
# define SWIFT_STDLIB_USING_GETENTROPY
#endif
#if defined(__linux__)
# include <linux/version.h>
# if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0))
# include <features.h>
# if defined(__BIONIC__) || (defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2,25))
# include <sys/random.h>
# define SWIFT_STDLIB_USING_GETRANDOM
# endif
# endif
#endif
#if defined(_WIN32) && !defined(__CYGWIN__)
# include <io.h>
# define WIN32_LEAN_AND_MEAN
# define WIN32_NO_STATUS
# include <Windows.h>
# undef WIN32_NO_STATUS
# include <Ntstatus.h>
# include <Ntdef.h>
# include <Bcrypt.h>
#else
# include <pthread.h>
# include <semaphore.h>
# include <sys/ioctl.h>
# include <unistd.h>
#endif
#include <cmath>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <random>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <type_traits>
#include "llvm/Support/DataTypes.h"
#include "swift/Basic/Lazy.h"
#include "swift/Runtime/Config.h"
#include "swift/Runtime/Debug.h"
#include "../SwiftShims/LibcShims.h"
#include "llvm/Support/DataTypes.h"
using namespace swift;
@@ -311,44 +343,72 @@ swift::_stdlib_cxx11_mt19937_uniform(__swift_uint32_t upper_bound) {
}
#if defined(__APPLE__)
#include <Security/Security.h>
SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) {
arc4random_buf(buf, nbytes);
}else {
} else {
OSStatus status = SecRandomCopyBytes(kSecRandomDefault, nbytes, buf);
if (status != errSecSuccess) {
fatalError(0, "Fatal error: SecRandomCopyBytes failed with error %d\n", status);
fatalError(0, "Fatal error: %d in '%s'\n", status, __func__);
}
}
}
#elif defined(__linux__)
#include <linux/version.h>
#include <sys/syscall.h>
#elif defined(_WIN32) && !defined(__CYGWIN__)
// TODO: Test on Windows
SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
auto _read = [&]() -> __swift_ssize_t {
return syscall(SYS_getrandom, buf, bytes, 0);
};
#else
auto _read = [&]() -> __swift_ssize_t {
static const int fd = _stdlib_open("/dev/urandom", O_RDONLY);
if (fd < 0) {
fatalError(0, "Unable to open '/dev/urandom'\n");
}
return _stdlib_read(fd, buf, bytes);
};
#endif
while (nbytes > 0) {
auto result = _read();
if (result < 1) {
if (errno == EINTR) { continue; }
fatalError(0, "Unable to read '/dev/urandom'\n");
}
buf = static_cast<uint8_t *>(buf) + result;
nbytes -= result;
NTSTATUS status = BCryptGenRandom(nullptr,
static_cast<PUCHAR>(buf),
static_cast<ULONG>(nbytes),
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (!NT_SUCCESS(status)) {
fatalError(0, "Fatal error: %#.8x in '%s'\n", status, __func__);
}
}
#elif defined(SWIFT_STDLIB_USING_GETENTROPY)
SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
while (nbytes > 0) {
constexpr __swift_size_t max_nbytes = 256;
__swift_size_t actual_nbytes = (nbytes < max_nbytes ?
nbytes : max_nbytes);
if (0 != getentropy(buf, actual_nbytes)) {
fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__);
}
buf = static_cast<uint8_t *>(buf) + actual_nbytes;
nbytes -= actual_nbytes;
}
}
#else
SWIFT_RUNTIME_STDLIB_INTERNAL
void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
#if !defined(SWIFT_STDLIB_USING_GETRANDOM)
static const int fd = _stdlib_open("/dev/urandom", O_RDONLY, 0);
if (fd < 0) {
fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__);
}
#endif
while (nbytes > 0) {
#if !defined(SWIFT_STDLIB_USING_GETRANDOM)
__swift_ssize_t actual_nbytes = _stdlib_read(fd, buf, nbytes);
#else
__swift_ssize_t actual_nbytes = getrandom(buf, nbytes, 0);
#endif
if (actual_nbytes < 1) {
if (errno == EINTR) { continue; }
fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__);
}
buf = static_cast<uint8_t *>(buf) + actual_nbytes;
nbytes -= actual_nbytes;
}
}
#endif

View File

@@ -15,7 +15,7 @@ RandomTests.test("basic random numbers") {
let randomDouble1 = Double.random(in: 0 ..< 1)
expectTrue(randomDouble1 < 1 && randomDouble1 >= 0)
let randomDouble2 = Double.random(in: 0 ..< 1)
expectTrue(randomDouble1 < 1 && randomDouble2 >= 0)
expectTrue(randomDouble2 < 1 && randomDouble2 >= 0)
expectTrue(randomDouble1 != randomDouble2)
}