Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/pecos-clib-pcg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ readme = "README.md"

[build-dependencies]
cc.workspace = true
ureq = { version = "2.9", default-features = false, features = ["native-tls"] }

[lints]
workspace = true
68 changes: 14 additions & 54 deletions crates/pecos-clib-pcg/build.rs
Original file line number Diff line number Diff line change
@@ -1,63 +1,23 @@
use cc::Build;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::path::PathBuf;

// TODO: Should probably just vendor the C code into the Rust crate...
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());

// Try local path first (for development)
let local_clib_path = manifest_dir
.parent()
.unwrap()
.parent()
.unwrap()
.join("clib")
.join("pecos-rng");

let src_path = if local_clib_path.exists() {
// Development: use local files
let src = local_clib_path.join("src");
println!("cargo:rerun-if-changed={}", src.join("rng_pcg.c").display());
println!("cargo:rerun-if-changed={}", src.join("rng_pcg.h").display());
src
} else {
// Published crate: download from GitHub
let pcg_dir = out_dir.join("pcg");
fs::create_dir_all(&pcg_dir).unwrap();

let commit = "95a6ddbdf85ad7bcf8b9133aa2552f3f1ae7da84";
let base_url = format!(
"https://raw.githubusercontent.com/PECOS-packages/PECOS/{commit}/clib/pecos-rng/src"
);

// Download files if they don't exist
download_if_needed(&pcg_dir.join("rng_pcg.c"), &format!("{base_url}/rng_pcg.c"));
download_if_needed(&pcg_dir.join("rng_pcg.h"), &format!("{base_url}/rng_pcg.h"));

pcg_dir
};
let c_src_dir = manifest_dir.join("c_src");

// Build our local C code with thread-safe functions exposed
println!(
"cargo:rerun-if-changed={}",
c_src_dir.join("rng_pcg.c").display()
);
println!(
"cargo:rerun-if-changed={}",
c_src_dir.join("rng_pcg.h").display()
);

Build::new()
.file(src_path.join("rng_pcg.c"))
.include(&src_path)
.file(c_src_dir.join("rng_pcg.c"))
.include(&c_src_dir)
.compile("pecos_pcg");
}

fn download_if_needed(path: &Path, url: &str) {
if !path.exists() {
println!("cargo:warning=Downloading {} to {}", url, path.display());

let response = ureq::get(url)
.call()
.unwrap_or_else(|e| panic!("Failed to download {url}: {e}"));

let mut file = fs::File::create(path)
.unwrap_or_else(|e| panic!("Failed to create {}: {}", path.display(), e));

std::io::copy(&mut response.into_reader(), &mut file)
.unwrap_or_else(|e| panic!("Failed to write {}: {}", path.display(), e));
}
}
92 changes: 92 additions & 0 deletions crates/pecos-clib-pcg/c_src/rng_pcg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* PCG Random Number Generation for C.
*
* Copyright 2014 Melissa O'Neill <[email protected]>
*
* 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.
*
* For additional information about the PCG random number generation scheme,
* including its license and other licensing options, visit
*
* http://www.pcg-random.org
*/

#include <math.h>
#include "rng_pcg.h"

// RNG state structure is now defined in the header

// global RNG state
static pcg32_random_t pcg32_global = {
0x853c49e6748fea9bULL,
0xda3e39cb94b95bdbULL
};

// default multi[plier]
#define PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL

// helper functions
static inline uint32_t pcg_rotr_32(uint32_t value, unsigned int urot) {
int rot = (int)urot;
return (value >> rot) | (value << ((-rot) & 31));
}

static inline void pcg_setseq_64_step_r(pcg32_random_t* rng) {
rng->state = rng->state * PCG_DEFAULT_MULTIPLIER_64 + rng->inc;
}

static inline uint32_t pcg_output_xsh_rr_64_32(uint64_t state) {
return pcg_rotr_32(((state >> 18u) ^ state) >> 27u, state >> 59u);
}

uint32_t pcg32_random_r(pcg32_random_t* rng) {
const uint64_t oldstate = rng->state;
pcg_setseq_64_step_r(rng);
return pcg_output_xsh_rr_64_32(oldstate);
}

uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t ubound) {
int32_t bound = (int32_t)ubound;
uint32_t threshold = -bound % bound;
for (;;) {
const uint32_t r = pcg32_random_r(rng);
if (r >= threshold)
return r % bound;
}
}

void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq) {
rng->state = 0U;
rng->inc = (initseq << 1u) | 1u;
pcg_setseq_64_step_r(rng);
rng->state += initstate;
pcg_setseq_64_step_r(rng);
}

// public interface to RNG

uint32_t pcg32_random() {
return pcg32_random_r(&pcg32_global);
}

uint32_t pcg32_boundedrand(uint32_t bound) {
return pcg32_boundedrand_r(&pcg32_global, bound);
}

double pcg32_frandom() {
return ldexp(pcg32_random(), -32);
}

void pcg32_srandom(uint64_t seq) {
pcg32_srandom_r(&pcg32_global, 42u, seq);
}
51 changes: 51 additions & 0 deletions crates/pecos-clib-pcg/c_src/rng_pcg.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* PCG Random Number Generation for C.
*
* Copyright 2014 Melissa O'Neill <[email protected]>
*
* 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.
*
* For additional information about the PCG random number generation scheme,
* including its license and other licensing options, visit
*
* http://www.pcg-random.org
*/

#pragma once

#include <stdint.h>

#if __cplusplus
extern "C" {
#endif

// Thread-safe RNG state structure
typedef struct pcg_state_setseq_64 {
uint64_t state;
uint64_t inc;
} pcg32_random_t;

// Thread-safe versions that take explicit state
uint32_t pcg32_random_r(pcg32_random_t* rng);
uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound);
void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq);

// Global state versions (for compatibility)
uint32_t pcg32_random();
uint32_t pcg32_boundedrand(uint32_t bound);
double pcg32_frandom();
void pcg32_srandom(uint64_t seq);

#if __cplusplus
}
#endif
70 changes: 61 additions & 9 deletions crates/pecos-clib-pcg/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,81 @@
// FFI bindings to the C PCG library
use std::cell::RefCell;

// Mirror the C struct pcg32_random_t
#[repr(C)]
struct PcgState {
state: u64,
inc: u64,
}

// FFI bindings to the C PCG library - now with thread-safe versions!
unsafe extern "C" {
fn pcg32_random() -> u32;
fn pcg32_boundedrand(bound: u32) -> u32;
fn pcg32_frandom() -> f64;
fn pcg32_srandom(seq: u64);
fn pcg32_random_r(rng: *mut PcgState) -> u32;
fn pcg32_boundedrand_r(rng: *mut PcgState, bound: u32) -> u32;
fn pcg32_srandom_r(rng: *mut PcgState, initstate: u64, initseq: u64);
}

// Thread-local state: each thread has its own PCG state
thread_local! {
static THREAD_STATE: RefCell<ThreadRngState> = RefCell::new(ThreadRngState::new());
}

struct ThreadRngState {
pcg: PcgState,
seed: u64,
}

impl ThreadRngState {
fn new() -> Self {
let mut state = Self {
pcg: PcgState { state: 0, inc: 0 },
seed: 0,
};
// Initialize with default seed
state.reseed(42);
state
}

fn reseed(&mut self, seed: u64) {
self.seed = seed;
unsafe {
// For PCG: initstate affects starting position, initseq selects sequence
// Using seed for initseq (sequence selection) and a fixed initstate
// matches the original behavior while allowing different sequences per thread
pcg32_srandom_r(&raw mut self.pcg, 42, seed);
}
}
}

// Rust wrapper functions with safe interfaces
#[must_use]
pub fn random() -> u32 {
unsafe { pcg32_random() }
THREAD_STATE.with(|state| {
let mut state = state.borrow_mut();
unsafe { pcg32_random_r(&raw mut state.pcg) }
})
}

#[must_use]
pub fn boundedrand(bound: u32) -> u32 {
unsafe { pcg32_boundedrand(bound) }
THREAD_STATE.with(|state| {
let mut state = state.borrow_mut();
unsafe { pcg32_boundedrand_r(&raw mut state.pcg, bound) }
})
}

#[must_use]
pub fn frandom() -> f64 {
unsafe { pcg32_frandom() }
// The C code implements this as ldexp(pcg32_random(), -32)
// which is equivalent to dividing by 2^32
THREAD_STATE.with(|state| {
let mut state = state.borrow_mut();
let val = unsafe { pcg32_random_r(&raw mut state.pcg) };
f64::from(val) * 2.0_f64.powi(-32)
})
}

pub fn srandom(seq: u64) {
unsafe { pcg32_srandom(seq) }
THREAD_STATE.with(|state| state.borrow_mut().reseed(seq));
}

#[cfg(test)]
Expand Down
Loading
Loading