rfc:rng_extension

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
Next revision Both sides next revision
rfc:rng_extension [2021/07/07 13:48]
zeriyoshi fix pseudo-implementation
rfc:rng_extension [2022/02/14 10:42]
zeriyoshi update 4.0
Line 1: Line 1:
-====== PHP RFC: Add Random Extension ====== +====== PHP RFC: Random Extension 4.0 ====== 
-  * Version: 2.2 +  * Version: 4.0 
-  * Date: 2021-05-18 +  * Date: 2022-02-14 
-  * Author: Go Kudo <zeriyoshi@gmail.com>+  * Author: Go Kudo <zeriyoshi@gmail.com> <g-kudo@colopl.co.jp>
   * Status: Under Discussion   * Status: Under Discussion
-  * Implementation: https://github.com/php/php-src/pull/7079+  * Implementation: https://github.com/php/php-src/pull/XXXX
   * First Published at: http://wiki.php.net/rfc/object_scope_prng   * First Published at: http://wiki.php.net/rfc/object_scope_prng
  
 ===== Introduction ===== ===== Introduction =====
-PHP is currently having problems with RNG reproducibility. 
  
-PHP's RNG has been unified into an implementation using the Mersenne twisterwith the rand() and srand() functions becoming aliases for mt_rand() and mt_srand() respectively in PHP 7.1.+PHP implements several useful RNGs. Howeverthey are currently only available in the global scope.
  
-Butthese functions still store the state in the global state of PHP and are not easily reproducible. Look at the following example.+Mersenne Twister, PHP's default RNG, provides a function mt_srand() to initialize with a user-specified seed value, but the scope is global, which may cause unintended user behavior.
  
-<code php> +When a user executes mt_srand()one would expect it to only affect result of mt_rand(), however, the following functions implicitly affect the result of mt_rand()
-echo foo(1234function (): void {}) . PHP_EOL; // Result: 1480009472 +
-echo foo(1234function (): void { mt_rand(); }) . PHP_EOL; // Result: 1747253290+
  
-function foo(int $seed, callable $bar): int { +  * shuffle() 
-    mt_srand($seed); +  * str_shuffle() 
-    $result = mt_rand()+  * array_rand()
-    $bar(); +
-    $result += mt_rand(); +
-    return $result; +
-+
-</code>+
  
-As mentioned above, the reproducibility of random numbers can easily be lost if additional processing is added later. +For examplein the following code, the result of the second mt_rand() is not reproducibleThis is because shuffle() uses a MT RNG internallywhich changes the state.
- +
-In addition, the fiber extension was introduced in PHP 8.1. This makes it more difficult to keep track of the execution orderHowever, this problem has existed since the introduced of Generator. +
- +
-There is also the problem of functions that implicitly use the state stored in PHP's global state. shuffle(), str_shuffle(), and array_rand() functions implicitly advance the state of a random number. This means that the following code is not reproducible, but it is difficult for the user to notice this.+
  
 <code php> <code php>
 mt_srand(1234); mt_srand(1234);
-echo mt_rand() . PHP_EOL// Result: 411284887+$next = mt_rand();
  
 mt_srand(1234); mt_srand(1234);
-str_shuffle('foobar'); +$arr = range(0, 9); 
-echo mt_rand() . PHP_EOL; // Result1314500282+shuffle($arr); 
 +$next2 = mt_rand()
 + 
 +die("next: ${next}, next2: ${next2}"); // next: 411284887, next21848274264
 </code> </code>
  
-===== Proposal ===== +These behaviors were unintuitive and often led to unintended execution results, but were not that problematic for general web application use.
-Implement and bundled Random extension into PHP.+
  
-The pseudo-implementation for the whole extension is as follows:+However, in more complex and repeatable applications (such as games), this can be a problem.
  
-<code php> +There is also the issue of state management difficulties with Fiber, which was added in PHP 8.1. Nikita had this to say:
-<?php+
  
-namespace Random +https://externals.io/message/115918#115959
-+
-    interface NumberGenerator +
-    { +
-        public function generate()int; +
-    } +
-}+
  
-namespace Random\NumberGenerator +In addition, the Mersenne Twister, can only generate 32-bit values. 
-{ +In recent years, many of the environments where PHP runs have been migrating to 64-bit platforms. 
-    class XorShift128Plus implements Random\NumberGenerator +In order to generate more secure values, an RNG that can generate 64-bit wide values should be provided by the language.
-    { +
-        public function __construct(?int $seed = null) {}+
  
-        public function generate(): int {}+===== Proposal =====
  
-        public function __serialize(): array {}+Implement the XorShift128Plus algorithm for generating new 64-bit wide random numbers, along with a random extension that includes an object scope RNG, and bundle it with PHP. 
 +XorShift128Plus is a fast, high-quality RNG that is proven in major web browsers.  
 +Many of the major hardware architectures are now 64-bit, so it makes sense to use this RNG.
  
-        public function __unserialize(array $data): void {} +In addition to the new algorithm, the following classes will be added to fix the global scope issue.
-    }+
  
-    class MT19937 implements Random\NumberGenerator +  * class Random\NumberGenerator\XorShift128Plus 
-    { +  * class Random\NumberGenerator\MersenneTwister 
-        public function __construct(?int $seed = null{}+  * class Random\NumberGenerator\CombinedLCG 
 +  * class Random\NumberGenerator\Secure (aka php_random_bytes())
  
-        public function generate(): int {}+These classes will hold independent RNG state and will not affect the global scope.
  
-        public function __serialize(): array {}+An interface Random\NumberGenerator is also added and are implmeneted by the classes above.  
 +This interface has only a single generate() method which makes it possible to switch between RNG implementations depending on the situation, 
 +allowing alternative implementations to be done by PHP in userland. This is useful, for example, for running tests.
  
-        public function __unserialize(array $data): void {} +RNGs other than XorShift128Plus are based on the RNGs currently implemented in PHP.
-    }+
  
-    class Secure implements Random\NumberGenerator +The Random\Randomizer class will be added to manipulate data using these RNGs. 
-    { +
-        public function __construct() {}+
  
-        public function generate()int {} +This class provides the following methods:
-    } +
-}+
  
-namespace +  * __constructor(\Random\NumberGenerator $generator = null) [defaults to XorShift128Plus if null] 
-{ +  * getInt(int $min, int $max): int [replacement for mt_rand() / rand()] 
-    final class Random +  * getBytes(int $length): string [replacement for random_bytes()] 
-    { +  * shuffleArray(array $array): array [replacement for shuffle()] 
-        private Random\NumberGenerator $rng;+  * shuffleString(string $string): string [replacement for str_shuffle()]
  
-        public function __construct(?Random\NumberGenerator $rng = null{} +Method equivalent to array_rand() was not implemented at this time because the implementation is complex and can be easily implemented in userland if necessary.
-        public function getNumberGenerator(): Random\NumberGenerator {} +
-        public function getInt(int $min, int $max): int {} +
-        public function getBytes(int $length): string {} +
-        public function shuffleArray(array $array): array {} +
-        public function shuffleString(string $string): string {} +
-        public function __serialize(): array {} +
-        public function __unserialize(array $data): void {} +
-    } +
-}+
  
-</code> +Examples of these uses are as follows:
- +
-Each RNG is implemented as a class in the Random\NumberGenerator namespace. They all implement the Random\NumberGenerator interface. +
- +
-The bundled RNGs are as follows: +
- +
-  * Random\NumberGenerator\XorShift128Plus: 64-bit, reproducible, PRNG. +
-  * Random\NumberGenerator\MT19937: 32-bit, reproducible, PRNG, compatible mt_srand() / mt_rand(). +
-  * Random\NumberGenerator\Secure: 64-bit, non-reproducible, CSPRNG, uses php_random_bytes() internally. +
- +
-Random class use a XorShift128+ by default. It can generate 64-bit values, is used by major browsers, and is fast and reliable.  +
-However, when used XorShift128+ in a 32-bit environment, the upper 32 bits are always truncated. This means that compatibility cannot be maintained between platforms, but this is not a problem since most platforms running PHP today are 64-bit and MT19937 can be used explicitly if compatibility is required. +
- +
-Note that (new Random\NumberGenerator\MT19937($seed))->generate() requires an additional bit shift to get a result equivalent to mt_rand(). mt_rand() implicitly did the bit-shifting internally, but there was no obvious reason for this. +
- +
-Secure is practically equivalent to random_int() and random_bytes(), This is useful when secure array or string shuffling is required. +
- +
-This class also supports RNGs defined in userland. It can be used by passing an instance of a class that implements the RandomNumberGenerator interface provided at the same time as the first argument.This is useful for unit testing or when you want to use a fixed number.+
  
 <code php> <code php>
 +// Use different RNGs for different environments.
 +$rng = $is_production
 +    ? new Random\NumberGenerator\Secure()
 +    : new Random\NumberGenerator\XorShift128Plus(1234);
  
-class UserDefinedRNG implements Random\NumberGenerator +$randomizer new Random\Randomizer($rng); 
-+$randomizer->shuffleString('foobar');
-    protected int $current 0; +
-     +
-    public function generate(): int +
-    { +
-        return ++$this->current; +
-    } +
-+
- +
-function foobar(Random\NumberGenerator $numberGenerator): void { +
-    for ($i = 0$i < 9; $i++) { +
-        echo $numberGenerator->generate(); +
-    } +
-+
- +
-foobar(new UserDefinedRNG()); // Results: 123456789+
 </code> </code>
  
-Also, as with MT, various alternative APIs using Random class will be provided.+<code php> 
 +// Safely migrate the existing mt_rand() state.
  
-<code c> +// before 
-/* similar php_mt_rand() *+mt_srand(1234MT_RAND_PHP); 
-uint64_t php_random_next(php_random *php_randombool shift);+foobar(); 
 +$result = str_shuffle('foobar');
  
-/* similar php_mt_rand_range() *+// after 
-zend_long php_random_range(php_random *php_randomzend_long min, zend_long max); +$randomizer = new Random\Randomizer(new Random\NumberGenerator\MersenneTwister(1234MT_RAND_PHP)); 
- +foobar(); 
-/* similar php_array_data_shuffle() */ +$result = $randomizer->stringShuffle('foobar');
-void php_random_array_data_shuffle(php_random *php_random, zval *array); +
- +
-/* similar php_string_shuffle() */ +
-void php_random_string_shuffle(php_random *php_random, char *str, zend_long len);+
 </code> </code>
  
-The Random class can be serialized using the standard PHP serialization mechanism. But, if the $rng member is not serializable, it will throws Exception.+As a side effect of this RFC, the following PHP functions have been moved to the new ext/random extension
  
-<code php> +This is because ext/standard/random.c reserves the name RANDOM and cannot be used by the extension. 
-// serialize +In additionall RNG-related implementations will be moved to the new random extension in order to standardize the RNG implementation.
-$foo = new Random(new Random\Numbergenerator\XorShift128Plus()); +
-for ($i = 0; $i < 10; $i++) { $foo->getInt(PHP_INT_MINPHP_INT_MAX);+
-var_dump(unserialize(serialize($foo))->getInt(PHP_INT_MIN, PHP_INT_MAX) === $foo->getInt(PHP_INT_MIN, PHP_INT_MAX)); // true+
  
-// can't serialize +  * lcg_value() 
-$foo = new Random(new Random\Numbergenerator\Secure()); +  * srand() 
-for ($i = 0; $i < 10; $i++{ $foo->getInt(PHP_INT_MIN, PHP_INT_MAX); } +  * rand() 
-var_dump(unserialize(serialize($foo))->getInt(PHP_INT_MIN, PHP_INT_MAX=== $foo->getInt(PHP_INT_MIN, PHP_INT_MAX)); // throws Exception:  Serialization of CLASS is not allowed. +  * mt_srand() 
-</code>+  * mt_rand() 
 +  * random_int() 
 +  * random_bytes()
  
-It is not possible to clone the Random class. it always throws Error (Error: Trying to clone an uncloneable object of class Random). This is because the standard PHP clone method copies the members by reference when cloning. This will be an unintended behavior for most users. Instead, you can use the getNumberGenerator() method to retrieve the internal RNG instance. The RNG instance can be cloned.+The following internal APIs will also be moved to the ext/random extension:
  
-<code php> +  * php_random_int_throw() 
-$foo = new Random();+  * php_random_int_silent() 
 +  * php_combined_lcg() 
 +  * php_mt_srand() 
 +  * php_mt_rand() 
 +  * php_mt_rand_range() 
 +  * php_mt_rand_common() 
 +  * php_srand() 
 +  * php_rand() 
 +  * php_random_bytes() 
 +  * php_random_int()
  
-// can't direct clone +All of these features are available from the extension by simply including a single ext/random/php_random.h
-// $bar = clone $foo;+
  
-// safe +The following header files are left in for extension compatibility. The contents all include ext/random/php_random.h.
-$bar = new Random(clone $foo->getNumberGenerator()); +
-</code>+
  
-Using this feature, the first example can be rewritten as follows: +  * ext/standard/php_lcg.h 
- +  * ext/standard/php_rand.h 
-<code php> +  * ext/standard/php_mt_rand.h 
-echo foo(1234, function (): void {}) . PHP_EOL; // Result: 1480009472 +  * ext/standard/php_random.h
-echo foo(1234, function (): void { mt_rand(); }) . PHP_EOL; // Result: 1480009472 +
- +
-function foo(int $seed, callable $bar): int { +
-    $numberGenerator = new Random\NumberGenerator\MT19937($seed); +
-    $result = ($numberGenerator->generate() >> 1); // requires bit-shift for compatibility+
-    $bar(); +
-    $result += ($numberGenerator->generate() >> 1); // requires bit-shift for compatibility. +
-    return $result; +
-+
-</code>+
  
 ===== Future Scope ===== ===== Future Scope =====
  
-This RFC will be the basis for making PHP RNGs safe in the future.+These are not within the scope of this RFC, but are worth considering in the future:
  
-By first accepted this RFCPHP gets a random number in the local scope.+  * Remove old header files for compatibility (php_lcg.hphp_rand.h, php_mt_rand.h, php_random.h) 
 +  * Deprecate lcg_value(), mt_srand(), srand()
  
-The Random class can also be used when new features are implemented that use random numbers. This has the effect of discouraging more implementations from using random numbers that depend on the global scope.+===== Backward Incompatible Changes =====
  
-More in the future, we can consider doing away with functions such as mt_srand(). These functions are simple and convenient, but they may unintentionally create implementations that depend on global scope. +The following names have been reserved and will no longer be available
- +
-===== Backward Incompatible Changes ===== +
-The following class name will no longer be available:+
  
   * "Random"   * "Random"
   * "Random\NumberGenerator"   * "Random\NumberGenerator"
   * "Random\NumberGenerator\XorShift128Plus"   * "Random\NumberGenerator\XorShift128Plus"
-  * "Random\NumberGenerator\MT19937"+  * "Random\NumberGenerator\MersenneTwister" 
 +  * "Random\NumberGenerator\CombinedLCG"
   * "Random\NumberGenerator\Secure"   * "Random\NumberGenerator\Secure"
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
-8.1+8.2
  
 ===== RFC Impact ===== ===== RFC Impact =====
Line 231: Line 166:
  
 ==== To Existing Extensions ==== ==== To Existing Extensions ====
-none+In the future, it may be necessary to change the included header files to point to ext/random/php_random.h. However, compatibility will be maintained for now.
  
 ==== To Opcache ==== ==== To Opcache ====
Line 246: Line 181:
  
 ===== Vote ===== ===== Vote =====
-Voting opens 2021-MM-DD and 2021-MM-DD at 00:00:00 EDT. 2/3 required to accept.+Voting opens 2022-MM-DD and 2021-MM-DD at 00:00:00 EDT. 2/3 required to accept.
  
-<doodle title="Add Random class" auth="zeriyoshi" voteType="single" closed="true"> +<doodle title="Add Random extension" auth="zeriyoshi" voteType="single" closed="true"> 
    * Yes    * Yes
    * No    * No
Line 254: Line 189:
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
-  * https://github.com/php/php-src/pull/7079+  * https://github.com/php/php-src/pull/XXXX
rfc/rng_extension.txt · Last modified: 2022/06/28 02:36 by zeriyoshi