Skip to content

Commit 1d03ed2

Browse files
committed
Implement Pointer conversions to and from GenMC. Limitation: Borrow tracking must be disabled. Remove hacks for keeping all memory allocations in GenMC mode.
1 parent 679cf9f commit 1d03ed2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1235
-88
lines changed

genmc-sys/build.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ mod downloading {
2828
/// The GenMC repository the we get our commit from.
2929
pub(crate) const GENMC_GITHUB_URL: &str = "https://gitlab.inf.ethz.ch/public-plf/genmc.git";
3030
/// The GenMC commit we depend on. It must be available on the specified GenMC repository.
31-
pub(crate) const GENMC_COMMIT: &str = "af9cc9ccd5d412b16defc35dbf36571c63a19c76";
31+
pub(crate) const GENMC_COMMIT: &str = "cd01c12032bdd71df742b41c7817f99acc72e7ab";
3232

3333
/// Ensure that a local GenMC repo is present and set to the correct commit.
3434
/// Return the path of the GenMC repo and whether the checked out commit was changed.
@@ -227,12 +227,16 @@ fn compile_cpp_dependencies(genmc_path: &Path, always_configure: bool) {
227227
// These definitions are parsed into a cmake list and then printed to the config.h file, so they are ';' separated.
228228
let definitions = llvm_definitions.split(";");
229229

230+
// These are all the C++ files we need to compile, which needs to be updated if more C++ files are added to Miri.
231+
// We use absolute paths since relative paths can confuse IDEs when attempting to go-to-source on a path in a compiler error.
232+
let cpp_files_base_path = Path::new("cpp/src/");
230233
let cpp_files = [
231-
"./cpp/src/MiriInterface/EventHandling.cpp",
232-
"./cpp/src/MiriInterface/Exploration.cpp",
233-
"./cpp/src/MiriInterface/Setup.cpp",
234-
"./cpp/src/MiriInterface/ThreadManagement.cpp",
235-
];
234+
"MiriInterface/EventHandling.cpp",
235+
"MiriInterface/Exploration.cpp",
236+
"MiriInterface/Setup.cpp",
237+
"MiriInterface/ThreadManagement.cpp",
238+
]
239+
.map(|file| std::path::absolute(cpp_files_base_path.join(file)).unwrap());
236240

237241
let mut bridge = cxx_build::bridge("src/lib.rs");
238242
// FIXME(genmc,cmake): Remove once the GenMC debug setting is available in the config.h file.

genmc-sys/cpp/include/MiriInterface.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ struct MiriGenmcShim : private GenMCDriver {
126126

127127
/**** Memory (de)allocation ****/
128128
auto handle_malloc(ThreadId thread_id, uint64_t size, uint64_t alignment) -> uint64_t;
129-
void handle_free(ThreadId thread_id, uint64_t address);
129+
auto handle_free(ThreadId thread_id, uint64_t address) -> bool;
130130

131131
/**** Thread management ****/
132132
void handle_thread_create(ThreadId thread_id, ThreadId parent_id);
@@ -257,20 +257,22 @@ namespace GenmcScalarExt {
257257
inline GenmcScalar uninit() {
258258
return GenmcScalar {
259259
.value = 0,
260+
.extra = 0,
260261
.is_init = false,
261262
};
262263
}
263264

264265
inline GenmcScalar from_sval(SVal sval) {
265266
return GenmcScalar {
266267
.value = sval.get(),
268+
.extra = sval.getExtra(),
267269
.is_init = true,
268270
};
269271
}
270272

271273
inline SVal to_sval(GenmcScalar scalar) {
272274
ERROR_ON(!scalar.is_init, "Cannot convert an uninitialized `GenmcScalar` into an `SVal`\n");
273-
return SVal(scalar.value);
275+
return SVal(scalar.value, scalar.extra);
274276
}
275277
} // namespace GenmcScalarExt
276278

genmc-sys/cpp/src/MiriInterface/EventHandling.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,9 @@ auto MiriGenmcShim::handle_malloc(ThreadId thread_id, uint64_t size, uint64_t al
250250
return ret_val.get();
251251
}
252252

253-
void MiriGenmcShim::handle_free(ThreadId thread_id, uint64_t address) {
253+
auto MiriGenmcShim::handle_free(ThreadId thread_id, uint64_t address) -> bool {
254254
const auto pos = inc_pos(thread_id);
255255
GenMCDriver::handleFree(pos, SAddr(address), EventDeps());
256-
// FIXME(genmc): add error handling once GenMC returns errors from `handleFree`
256+
// FIXME(genmc): use returned error from `handleFree` once implemented in GenMC.
257+
return getResult().status.has_value();
257258
}

genmc-sys/cpp/src/MiriInterface/Exploration.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ auto MiriGenmcShim::schedule_next(
2424

2525
if (const auto result = GenMCDriver::scheduleNext(threads_action_))
2626
return SchedulingResult { ExecutionState::Ok, static_cast<int32_t>(result.value()) };
27-
if (GenMCDriver::isExecutionBlocked())
27+
if (getExec().getGraph().isBlocked())
2828
return SchedulingResult { ExecutionState::Blocked, 0 };
29+
if (getResult().status.has_value()) // the "value" here is a `VerificationError`
30+
return SchedulingResult { ExecutionState::Error, 0 };
2931
return SchedulingResult { ExecutionState::Finished, 0 };
3032
}
3133

genmc-sys/cpp/src/MiriInterface/Setup.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,8 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
170170
// From a Miri perspective, this API doesn't work very well: most memory starts out
171171
// "uninitialized";
172172
// only statics have an initial value. And their initial value is just a sequence of bytes,
173-
// but GenMC
174-
// expect this to be already split into separate atomic variables. So we return a dummy
175-
// value.
173+
// but GenMC expect this to be already split into separate atomic variables. So we return a
174+
// dummy value.
176175
// This value should never be visible to the interpreted program.
177176
// GenMC does not understand uninitialized memory the same way Miri does, which may cause
178177
// this function to be called. The returned value can be visible to Miri or the user:
@@ -183,13 +182,14 @@ static auto to_genmc_verbosity_level(const LogLevel log_level) -> VerbosityLevel
183182
// Currently, atomic loads can see this value, unless initialized by an *atomic* store.
184183
// FIXME(genmc): update this comment once mixed atomic-non-atomic support is added.
185184
//
186-
// FIXME(genmc): implement proper support for uninitialized memory in GenMC. Ideally, the
187-
// initial value getter would return an `optional<SVal>`, since the memory location may be
188-
// uninitialized.
185+
// FIXME(genmc): implement proper support for uninitialized memory in GenMC.
186+
// Ideally, the initial value getter would return an `optional<SVal>`, since the memory
187+
// location may be uninitialized.
189188
.initValGetter = [](const AAccess& a) { return SVal(0xDEAD); },
190-
// Miri serves non-atomic loads from its own memory and these GenMC checks are wrong in
191-
// that case. This should no longer be required with proper mixed-size access support.
192-
.skipUninitLoadChecks = [](MemOrdering ord) { return ord == MemOrdering::NotAtomic; },
189+
// Miri serves non-atomic loads from its own memory and these GenMC checks are wrong in that
190+
// case. This should no longer be required with proper mixed-size access support.
191+
.skipUninitLoadChecks = [](const MemAccessLabel* access_label
192+
) { return access_label->getOrdering() == MemOrdering::NotAtomic; },
193193
};
194194
driver->setInterpCallbacks(std::move(interpreter_callbacks));
195195

genmc-sys/src/lib.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ pub fn create_genmc_driver_handle(
4545
}
4646

4747
impl GenmcScalar {
48-
pub const UNINIT: Self = Self { value: 0, is_init: false };
48+
pub const UNINIT: Self = Self { value: 0, extra: 0, is_init: false };
4949

5050
pub const fn from_u64(value: u64) -> Self {
51-
Self { value, is_init: true }
51+
Self { value, extra: 0, is_init: true }
52+
}
53+
54+
pub const fn has_provenance(&self) -> bool {
55+
self.extra != 0
5256
}
5357
}
5458

@@ -162,17 +166,24 @@ mod ffi {
162166
}
163167

164168
/// This type corresponds to `Option<SVal>` (or `std::optional<SVal>`), where `SVal` is the type that GenMC uses for storing values.
165-
/// CXX doesn't support `std::optional` currently, so we need to use an extra `bool` to define whether this value is initialized or not.
166169
#[derive(Debug, Clone, Copy)]
167170
struct GenmcScalar {
171+
/// The raw byte-level value (discarding provenance, if any) of this scalar.
168172
value: u64,
173+
/// This is zero for integer values. For pointers, this encodes the provenance by
174+
/// storing the base address of the allocation that this pointer belongs to.
175+
/// Operations on `SVal` in GenMC (e.g., `fetch_add`) preserve the `extra` of the left argument (`left.fetch_add(right, ...)`).
176+
extra: u64,
177+
/// Indicates whether this value is initialized. If this is `false`, the other fields do not matter.
178+
/// (Ideally we'd use `std::optional` but CXX does not support that.)
169179
is_init: bool,
170180
}
171181

172182
#[must_use]
173183
#[derive(Debug, Clone, Copy)]
174184
enum ExecutionState {
175185
Ok,
186+
Error,
176187
Blocked,
177188
Finished,
178189
}
@@ -406,7 +417,8 @@ mod ffi {
406417
size: u64,
407418
alignment: u64,
408419
) -> u64;
409-
fn handle_free(self: Pin<&mut MiriGenmcShim>, thread_id: i32, address: u64);
420+
/// Returns true if an error was found.
421+
fn handle_free(self: Pin<&mut MiriGenmcShim>, thread_id: i32, address: u64) -> bool;
410422

411423
/**** Thread management ****/
412424
fn handle_thread_create(self: Pin<&mut MiriGenmcShim>, thread_id: i32, parent_id: i32);

src/alloc_addresses/mod.rs

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,7 @@ impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
207207
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
208208
// Returns the `AllocId` that corresponds to the specified addr,
209209
// or `None` if the addr is out of bounds.
210-
// Setting `only_exposed_allocations` selects whether only exposed allocations are considered.
211-
fn alloc_id_from_addr(
212-
&self,
213-
addr: u64,
214-
size: i64,
215-
only_exposed_allocations: bool,
216-
) -> Option<AllocId> {
210+
fn alloc_id_from_addr(&self, addr: u64, size: i64) -> Option<AllocId> {
217211
let this = self.eval_context_ref();
218212
let global_state = this.machine.alloc_addresses.borrow();
219213
assert!(global_state.provenance_mode != ProvenanceMode::Strict);
@@ -242,13 +236,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
242236
}
243237
}?;
244238

245-
// We only use this provenance if it has been exposed, or if the caller requested also non-exposed allocations
246-
if !only_exposed_allocations || global_state.exposed.contains(&alloc_id) {
239+
// We only use this provenance if it has been exposed.
240+
if global_state.exposed.contains(&alloc_id) {
247241
// This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed.
248-
// In GenMC mode, we keep all allocations, so this check doesn't apply there.
249-
if this.machine.data_race.as_genmc_ref().is_none() {
250-
debug_assert!(this.is_alloc_live(alloc_id));
251-
}
242+
debug_assert!(this.is_alloc_live(alloc_id));
252243
Some(alloc_id)
253244
} else {
254245
None
@@ -443,8 +434,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
443434
alloc_id
444435
} else {
445436
// A wildcard pointer.
446-
let only_exposed_allocations = true;
447-
this.alloc_id_from_addr(addr.bytes(), size, only_exposed_allocations)?
437+
this.alloc_id_from_addr(addr.bytes(), size)?
448438
};
449439

450440
// This cannot fail: since we already have a pointer with that provenance, adjust_alloc_root_pointer
@@ -465,13 +455,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
465455

466456
impl<'tcx> MiriMachine<'tcx> {
467457
pub fn free_alloc_id(&mut self, dead_id: AllocId, size: Size, align: Align, kind: MemoryKind) {
468-
// In GenMC mode, we can't remove dead allocation info since such pointers can
469-
// still be stored in atomics and we need this info to convert GenMC pointers to Miri pointers.
470-
// `global_state.reuse` is also unused so we can just skip this entire function.
471-
if self.data_race.as_genmc_ref().is_some() {
472-
return;
473-
}
474-
475458
let global_state = self.alloc_addresses.get_mut();
476459
let rng = self.rng.get_mut();
477460

src/concurrency/genmc/helper.rs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ use rustc_abi::Size;
55
use rustc_const_eval::interpret::{InterpResult, interp_ok};
66
use rustc_data_structures::fx::FxHashSet;
77
use rustc_middle::mir;
8+
use rustc_middle::mir::interpret;
89
use rustc_middle::ty::ScalarInt;
910
use rustc_span::Span;
1011
use tracing::debug;
1112

1213
use super::GenmcScalar;
13-
use crate::diagnostics::EvalContextExt;
14+
use crate::alloc_addresses::EvalContextExt as _;
15+
use crate::diagnostics::EvalContextExt as _;
1416
use crate::intrinsics::AtomicRmwOp;
1517
use crate::{
16-
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, InterpCx, MiriInterpCx,
17-
MiriMachine, NonHaltingDiagnostic, Scalar, throw_unsup_format,
18+
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, BorTag, GenmcCtx, InterpCx,
19+
MiriInterpCx, MiriMachine, NonHaltingDiagnostic, Scalar, machine, throw_unsup_format,
1820
};
1921

2022
/// Maximum size memory access in bytes that GenMC supports.
@@ -80,19 +82,30 @@ pub fn split_access(address: Size, size: Size) -> impl Iterator<Item = (u64, u64
8082
/// We cannot use the `AllocId` instead of the base address, since Miri has no control over the `AllocId`, and it may change across executions.
8183
/// Pointers with `Wildcard` provenance are not supported.
8284
pub fn scalar_to_genmc_scalar<'tcx>(
83-
_ecx: &MiriInterpCx<'tcx>,
85+
ecx: &MiriInterpCx<'tcx>,
86+
genmc_ctx: &GenmcCtx,
8487
scalar: Scalar,
8588
) -> InterpResult<'tcx, GenmcScalar> {
8689
interp_ok(match scalar {
8790
rustc_const_eval::interpret::Scalar::Int(scalar_int) => {
8891
// FIXME(genmc): Add u128 support once GenMC supports it.
8992
let value: u64 = scalar_int.to_uint(scalar_int.size()).try_into().unwrap();
90-
GenmcScalar { value, is_init: true }
93+
GenmcScalar { value, extra: 0, is_init: true }
94+
}
95+
rustc_const_eval::interpret::Scalar::Ptr(pointer, size) => {
96+
// FIXME(genmc,borrow tracking): Borrow tracking information is lost.
97+
let addr = crate::Pointer::from(pointer).addr();
98+
if let crate::Provenance::Wildcard = pointer.provenance {
99+
throw_unsup_format!("Pointers with wildcard provenance not allowed in GenMC mode");
100+
}
101+
let (alloc_id, _size, _prov_extra) =
102+
rustc_const_eval::interpret::Machine::ptr_get_alloc(ecx, pointer, size.into())
103+
.unwrap();
104+
let base_addr = ecx.addr_from_alloc_id(alloc_id, None)?;
105+
// Add the base_addr alloc_id pair to the map.
106+
genmc_ctx.exec_state.genmc_shared_allocs_map.borrow_mut().insert(base_addr, alloc_id);
107+
GenmcScalar { value: addr.bytes(), extra: base_addr, is_init: true }
91108
}
92-
rustc_const_eval::interpret::Scalar::Ptr(_pointer, _size) =>
93-
throw_unsup_format!(
94-
"FIXME(genmc): Implement sending pointers (with provenance) to GenMC."
95-
),
96109
})
97110
}
98111

@@ -101,16 +114,25 @@ pub fn scalar_to_genmc_scalar<'tcx>(
101114
/// Convert a `GenmcScalar` back into a Miri `Scalar`.
102115
/// For pointers, attempt to convert the stored base address of their allocation back into an `AllocId`.
103116
pub fn genmc_scalar_to_scalar<'tcx>(
104-
_ecx: &MiriInterpCx<'tcx>,
117+
ecx: &MiriInterpCx<'tcx>,
118+
genmc_ctx: &GenmcCtx,
105119
scalar: GenmcScalar,
106120
size: Size,
107121
) -> InterpResult<'tcx, Scalar> {
108-
// FIXME(genmc): Add GenmcScalar to Miri Pointer conversion.
109-
110-
// NOTE: GenMC always returns 64 bit values, and the upper bits are not yet truncated.
111-
// FIXME(genmc): GenMC should be doing the truncation, not Miri.
112-
let (value_scalar_int, _got_truncated) = ScalarInt::truncate_from_uint(scalar.value, size);
113-
interp_ok(Scalar::Int(value_scalar_int))
122+
// If `extra` is zero, we have a regular integer.
123+
if scalar.extra == 0 {
124+
// NOTE: GenMC always returns 64 bit values, and the upper bits are not yet truncated.
125+
// FIXME(genmc): GenMC should be doing the truncation, not Miri.
126+
let (value_scalar_int, _got_truncated) = ScalarInt::truncate_from_uint(scalar.value, size);
127+
return interp_ok(Scalar::from(value_scalar_int));
128+
}
129+
// `extra` is non-zero, we have a pointer.
130+
// When we get a pointer from GenMC, then we must have sent it to GenMC before in the same execution (since the reads-from relation is always respected).
131+
let alloc_id = genmc_ctx.exec_state.genmc_shared_allocs_map.borrow()[&scalar.extra];
132+
// FIXME(genmc,borrow tracking): Borrow tracking not yet supported.
133+
let provenance = machine::Provenance::Concrete { alloc_id, tag: BorTag::default() };
134+
let ptr = interpret::Pointer::new(provenance, Size::from_bytes(scalar.value));
135+
interp_ok(Scalar::from_pointer(ptr, &ecx.tcx))
114136
}
115137

116138
impl AtomicReadOrd {

0 commit comments

Comments
 (0)