Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic support for concurrency (Linux/macos only) #1284

Merged
merged 77 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
82f17ab
Implement basic support for concurrency (Linux only).
Mar 16, 2020
58a6a27
Add a warning that Miri does not check for data-races.
Apr 1, 2020
8dd8f19
Update to support the updated API.
Apr 1, 2020
92946b5
Add a test for thread locals.
Apr 1, 2020
aef4c95
Fix the problem of sending pointed to thread local statics. Add a reg…
Apr 3, 2020
1f33f04
Move pthread_create and related shims to a separate file.
Apr 6, 2020
ed9c7d1
Report that we do not support foreign thread local statics.
Apr 6, 2020
5218419
Fix comments in TLS.
Apr 6, 2020
f21197f
Store the thread name.
Apr 6, 2020
b04bf8a
Rustfmt the test.
Apr 6, 2020
2202278
Fix pthread_self.
Apr 6, 2020
1c8a59c
Rebase on PR 1157.
Apr 8, 2020
d907fb5
Rename ThreadSet to ThreadManager.
Apr 9, 2020
0c4303c
Small refactoring in pthread sync: extract common functionallity to s…
Apr 9, 2020
963e969
Generate fresh allocation ids for thread locals in eval_maybe_thread_…
Apr 15, 2020
51b16e5
Generate thread local allocations in eval_maybe_thread_local_static_c…
Apr 15, 2020
325c31e
Address some of the reviewers comments.
Apr 16, 2020
4609c3c
Rename eval_maybe_thread_local_static_const to adjust_global_const.
Apr 16, 2020
d9ec0f2
Add a missing newline in the test.
Apr 16, 2020
552080a
Fix imports.
Apr 16, 2020
94118d4
Make an assert message consistent with other asserts.
Apr 16, 2020
1d0eb93
Fix typo in a comment.
Apr 16, 2020
688cacb
Cleanup the implementation of adjust_global_const.
Apr 16, 2020
a585dc8
Add a missing newline.
Apr 16, 2020
44e9305
Rename threads to thread to match the Rust standard library.
Apr 16, 2020
d062f63
Fix support for MacOS.
Apr 17, 2020
134533d
Add a comment explaining global destructors on MacOS.
Apr 17, 2020
46fd333
Implement thread::yield_now.
Apr 18, 2020
421be27
Add concurrency tests.
Apr 18, 2020
c84b289
Update a comment in README about what concurrency checks we support.
Apr 19, 2020
d6c0392
Rename MacOS set global dtor function.
Apr 19, 2020
69df2e1
Move prctl to Linux specific shims.
Apr 19, 2020
eab38df
Change the warning message.
Apr 19, 2020
75e6549
Improve prctl, add a test.
Apr 19, 2020
94cbe88
Many small changes to thread management.
Apr 19, 2020
80459bb
Improve concurrency tests.
Apr 19, 2020
17f7bc8
Fix how a pthread_create function argument is constructed.
Apr 19, 2020
5b55e07
Add more concurrency tests.
Apr 19, 2020
e4dc356
Track if a thread was already joined.
Apr 19, 2020
9a01c3f
Clarify comments about TLS destructor handling in Windows, add a test…
Apr 20, 2020
3bb1657
Small style fix.
Apr 20, 2020
452e36e
Print the thread name in Debug.
Apr 20, 2020
69eaaad
Fix merge error.
Apr 20, 2020
e7c2694
Make the main thread detached.
Apr 20, 2020
e7b82fd
Fix the test annotation.
Apr 20, 2020
40e50bf
Clarify test comments.
Apr 20, 2020
8a7dbde
Check prctl argument types and fix the test.
Apr 20, 2020
d45e985
Clarify FIXME.
Apr 20, 2020
eaa6326
Make multiple threads to try to join a thread while it is still running.
Apr 21, 2020
cc9248a
Ignore prctl test on MacOS because it does not support it.
Apr 21, 2020
90e9a87
Add an explanatory comment to the test.
Apr 21, 2020
8240ed2
Change the test not to rely on internals.
Apr 21, 2020
feb1883
Unify TLS dtors; move stepping outside.
Apr 24, 2020
04abf06
Move copying of the thread name to the client side.
Apr 24, 2020
bc9d007
Improve Debug formatting of the thread name.
Apr 24, 2020
ff5e35b
Added a test that joining main is UB.
Apr 26, 2020
64164b1
Improve comments.
Apr 26, 2020
60cd8aa
Delete a duplicate test.
Apr 26, 2020
39efdf3
Move prctl test to the same file as other libc tests.
Apr 26, 2020
6842eb2
Rename global tls dtor to thread dtor.
Apr 26, 2020
c4574dd
Many small changes to clean up code.
Apr 26, 2020
911ff7e
Improve style and comments.
Apr 27, 2020
d9e18ad
Make sure to remove thread local data only if we have destructor.
Apr 27, 2020
174adad
Use DLL_THREAD_DETACH when calling windows TLS destructor.
Apr 27, 2020
9ba3ef2
Change representation and conversion of ThreadId and BlockSetId.
Apr 27, 2020
207c6e7
Improve comments and code clarity.
Apr 27, 2020
356aecc
Add a FIXME.
Apr 27, 2020
f204b67
Merge dtors_running and last_dtor_key fields.
Apr 27, 2020
331dbd1
Add a test for joining in a destructor.
Apr 27, 2020
c56ef31
Improve comments.
Apr 27, 2020
df2ca53
Make From implementations non-failing.
Apr 27, 2020
1355574
Delete remaining tls entries after all destructors completed.
Apr 27, 2020
3b58541
Fix MacOS and Windows builds.
Apr 27, 2020
46b0317
Improve code readability and comments.
Apr 29, 2020
0e052ab
Use Entry API in set_dtors_running.
Apr 29, 2020
603ec0b
Fix a regression in Windows dtors.
Apr 29, 2020
48da0cf
Fix prctl SET_NAME and GET_NAME behaviour.
Apr 30, 2020
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
2 changes: 1 addition & 1 deletion src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
assert!(ecx.step()?, "a terminated thread was scheduled for execution");
}
SchedulingAction::ExecuteDtors => {
ecx.schedule_tls_dtors_for_active_thread()?;
ecx.schedule_next_tls_dtor_for_active_thread()?;
vakaras marked this conversation as resolved.
Show resolved Hide resolved
}
SchedulingAction::Stop => {
break;
Expand Down
2 changes: 1 addition & 1 deletion src/shims/foreign_items/posix/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let dtor = this.memory.get_fn(dtor)?.as_instance()?;
let data = this.read_scalar(args[1])?.not_undef()?;
let active_thread = this.get_active_thread()?;
this.machine.tls.set_thread_global_dtor(active_thread, dtor, data)?;
this.machine.tls.set_thread_dtor(active_thread, dtor, data)?;
}

// Querying system information
Expand Down
22 changes: 13 additions & 9 deletions src/shims/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ fn mutexattr_set_kind<'mir, 'tcx: 'mir>(
// bytes 8-11: when count > 0, id of the owner thread as a u32
// bytes 12-15 or 16-19 (depending on platform): mutex kind, as an i32
// (the kind has to be at its offset for compatibility with static initializer macros)
// bytes 20-23: when count > 0, id of the blockset in which the blocked threads are waiting.
// bytes 20-23: when count > 0, id of the blockset in which the blocked threads
// are waiting or 0 if blockset is not yet assigned.

const PTHREAD_MUTEX_T_MIN_SIZE: u64 = 24;

Expand Down Expand Up @@ -158,7 +159,7 @@ fn mutex_get_or_create_blockset<'mir, 'tcx: 'mir>(
mutex_set_blockset(ecx, mutex_op, blockset.to_u32_scalar())?;
Ok(blockset)
} else {
Ok(blockset.into())
Ok(BlockSetId::new(blockset))
}
}

Expand All @@ -170,9 +171,9 @@ fn mutex_get_or_create_blockset<'mir, 'tcx: 'mir>(
// bytes 4-7: reader count, as a u32
// bytes 8-11: writer count, as a u32
// bytes 12-15: when writer or reader count > 0, id of the blockset in which the
// blocked writers are waiting.
// blocked writers are waiting or 0 if blockset is not yet assigned.
// bytes 16-20: when writer count > 0, id of the blockset in which the blocked
// readers are waiting.
// readers are waiting or 0 if blockset is not yet assigned.

const PTHREAD_RWLOCK_T_MIN_SIZE: u64 = 20;

Expand Down Expand Up @@ -233,7 +234,7 @@ fn rwlock_get_or_create_writer_blockset<'mir, 'tcx: 'mir>(
rwlock_set_writer_blockset(ecx, rwlock_op, blockset.to_u32_scalar())?;
Ok(blockset)
} else {
Ok(blockset.into())
Ok(BlockSetId::new(blockset))
}
}

Expand Down Expand Up @@ -264,7 +265,7 @@ fn rwlock_get_or_create_reader_blockset<'mir, 'tcx: 'mir>(
rwlock_set_reader_blockset(ecx, rwlock_op, blockset.to_u32_scalar())?;
Ok(blockset)
} else {
Ok(blockset.into())
Ok(BlockSetId::new(blockset))
}
}

Expand Down Expand Up @@ -342,8 +343,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
Ok(0)
} else {
// The mutex is locked. Let's check by whom.
let owner_thread: ThreadId =
mutex_get_owner(this, mutex_op)?.not_undef()?.to_u32()?.into();
let owner_thread: ThreadId = mutex_get_owner(this, mutex_op)?.to_u32()?.into();
if owner_thread != active_thread {
// Block the active thread.
let blockset = mutex_get_or_create_blockset(this, mutex_op)?;
Expand Down Expand Up @@ -425,6 +425,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
mutex_set_owner(this, mutex_op, new_owner.to_u32_scalar())?;
} else {
// No thread is waiting on this mutex.
mutex_set_owner(this, mutex_op, Scalar::from_u32(0))?;
mutex_set_locked_count(this, mutex_op, Scalar::from_u32(0))?;
vakaras marked this conversation as resolved.
Show resolved Hide resolved
}
Ok(0)
Expand Down Expand Up @@ -550,10 +551,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
Ok(0)
} else if writers != 0 {
let reader_blockset = rwlock_get_or_create_reader_blockset(this, rwlock_op)?;
rwlock_set_writers(this, rwlock_op, Scalar::from_u32(0))?;
// We are prioritizing writers here against the readers. As a
// result, not only readers can starve writers, but also writers can
// starve readers.
if let Some(_writer) = this.unblock_some_thread(writer_blockset)? {
rwlock_set_writers(this, rwlock_op, Scalar::from_u32(1))?;
vakaras marked this conversation as resolved.
Show resolved Hide resolved
vakaras marked this conversation as resolved.
Show resolved Hide resolved
} else {
rwlock_set_writers(this, rwlock_op, Scalar::from_u32(0))?;
let mut readers = 0;
while let Some(_reader) = this.unblock_some_thread(reader_blockset)? {
readers += 1;
Expand Down
56 changes: 20 additions & 36 deletions src/shims/thread.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::convert::TryInto;

use crate::*;
use rustc_index::vec::Idx;
use rustc_target::abi::LayoutOf;

impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
Expand All @@ -19,33 +20,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
);

let new_thread_id = this.create_thread()?;
// Also switch to new thread so that we can push the first stackframe.
let old_thread_id = this.set_active_thread(new_thread_id)?;
vakaras marked this conversation as resolved.
Show resolved Hide resolved

let thread_info_place = this.deref_operand(thread)?;
let thread_info_type = thread.layout.ty
.builtin_deref(true)
.ok_or_else(|| err_ub_format!(
"wrong signature used for `pthread_create`: first argument must be a raw pointer."
))?
.ty;
let thread_info_layout = this.layout_of(thread_info_type)?;
this.write_scalar(
Scalar::from_uint(new_thread_id.index() as u128, thread_info_layout.size),
Scalar::from_uint(new_thread_id.to_u128(), thread_info_place.layout.size),
thread_info_place.into(),
)?;

let fn_ptr = this.read_scalar(start_routine)?.not_undef()?;
let instance = this.memory.get_fn(fn_ptr)?.as_instance()?;

let func_arg = this.read_immediate(arg)?;
let func_args = [*func_arg];

// Note: the returned value is currently ignored (see the FIXME in
// pthread_join below) because the Rust standard library does not use
// it.
let ret_place =
this.allocate(this.layout_of(this.tcx.types.usize)?, MiriMemoryKind::Machine.into());

this.call_function(
instance,
&func_args[..],
&[*func_arg],
Some(ret_place.into()),
StackPopCleanup::None { cleanup: true },
)?;
Expand All @@ -63,20 +60,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let this = self.eval_context_mut();

if !this.is_null(this.read_scalar(retval)?.not_undef()?)? {
// FIXME: implement reading the thread function's return place.
throw_unsup_format!("Miri supports pthread_join only with retval==NULL");
vakaras marked this conversation as resolved.
Show resolved Hide resolved
}

let thread_id = this.read_scalar(thread)?.not_undef()?.to_machine_usize(this)?;
this.join_thread(thread_id.into())?;
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
this.join_thread(thread_id.try_into().expect("thread ID should fit in u32"))?;

Ok(0)
}

fn pthread_detach(&mut self, thread: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();

let thread_id = this.read_scalar(thread)?.not_undef()?.to_machine_usize(this)?;
this.detach_thread(thread_id.into())?;
let thread_id = this.read_scalar(thread)?.to_machine_usize(this)?;
this.detach_thread(thread_id.try_into().expect("thread ID should fit in u32"))?;

Ok(0)
}
Expand All @@ -85,44 +83,30 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let this = self.eval_context_mut();

let thread_id = this.get_active_thread()?;
this.write_scalar(Scalar::from_uint(thread_id.index() as u128, dest.layout.size), dest)
this.write_scalar(Scalar::from_uint(thread_id.to_u128(), dest.layout.size), dest)
}

fn prctl(
&mut self,
option: OpTy<'tcx, Tag>,
arg2: OpTy<'tcx, Tag>,
arg3: OpTy<'tcx, Tag>,
arg4: OpTy<'tcx, Tag>,
arg5: OpTy<'tcx, Tag>,
_arg3: OpTy<'tcx, Tag>,
_arg4: OpTy<'tcx, Tag>,
_arg5: OpTy<'tcx, Tag>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();

// prctl last 5 arguments are declared as variadic. Therefore, we need
// to check their types manually.
let c_long_size = this.libc_ty_layout("c_long")?.size.bytes();
let check_arg = |arg: OpTy<'tcx, Tag>| -> InterpResult<'tcx> {
match this.read_scalar(arg)?.not_undef()? {
Scalar::Raw { size, .. } if u64::from(size) == c_long_size => Ok(()),
_ => throw_ub_format!("an argument of unsupported type was passed to prctl"),
}
};
check_arg(arg2)?;
check_arg(arg3)?;
check_arg(arg4)?;
check_arg(arg5)?;

let option = this.read_scalar(option)?.not_undef()?.to_i32()?;
let option = this.read_scalar(option)?.to_i32()?;
if option == this.eval_libc_i32("PR_SET_NAME")? {
let address = this.read_scalar(arg2)?.not_undef()?;
let name = this.memory.read_c_str(address)?.to_owned();
vakaras marked this conversation as resolved.
Show resolved Hide resolved
this.set_active_thread_name(name)?;
} else if option == this.eval_libc_i32("PR_GET_NAME")? {
let address = this.read_scalar(arg2)?.not_undef()?;
let name = this.get_active_thread_name()?;
let name = this.get_active_thread_name()?.to_vec();
this.memory.write_bytes(address, name)?;
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
vakaras marked this conversation as resolved.
Show resolved Hide resolved
} else {
throw_unsup_format!("Unsupported prctl option.");
throw_unsup_format!("unsupported prctl option {}", option);
}

Ok(0)
Expand Down
Loading