<commit_before>//! This module contains everything needed to instantiate an interpreter.
//! This separation exists to ensure that no fancy miri features like
//! interpreting common C functions leak into CTFE.

use std::borrow::{Borrow, Cow};
use std::hash::Hash;

use rustc::hir::def_id::DefId;
use rustc::mir;
use rustc::ty::{self, TyCtxt};

use super::{
    Allocation, AllocId, InterpResult, Scalar, AllocationExtra,
    InterpCx, PlaceTy, OpTy, ImmTy, MemoryKind, Pointer, Memory
};

/// Whether this kind of memory is allowed to leak
pub trait MayLeak: Copy {
    fn may_leak(self) -> bool;
}

/// The functionality needed by memory to manage its allocations
pub trait AllocMap<K: Hash + Eq, V> {
    /// Tests if the map contains the given key.
    /// Deliberately takes `&mut` because that is sufficient, and some implementations
    /// can be more efficient then (using `RefCell::get_mut`).
    fn contains_key<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> bool
        where K: Borrow<Q>;

    /// Inserts a new entry into the map.
    fn insert(&mut self, k: K, v: V) -> Option<V>;

    /// Removes an entry from the map.
    fn remove<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> Option<V>
        where K: Borrow<Q>;

    /// Returns data based the keys and values in the map.
    fn filter_map_collect<T>(&self, f: impl FnMut(&K, &V) -> Option<T>) -> Vec<T>;

    /// Returns a reference to entry `k`. If no such entry exists, call
    /// `vacant` and either forward its error, or add its result to the map
    /// and return a reference to *that*.
    fn get_or<E>(
        &self,
        k: K,
        vacant: impl FnOnce() -> Result<V, E>
    ) -> Result<&V, E>;

    /// Returns a mutable reference to entry `k`. If no such entry exists, call
    /// `vacant` and either forward its error, or add its result to the map
    /// and return a reference to *that*.
    fn get_mut_or<E>(
        &mut self,
        k: K,
        vacant: impl FnOnce() -> Result<V, E>
    ) -> Result<&mut V, E>;
}

/// Methods of this trait signifies a point where CTFE evaluation would fail
/// and some use case dependent behaviour can instead be applied.
pub trait Machine<'mir, 'tcx>: Sized {
    /// Additional memory kinds a machine wishes to distinguish from the builtin ones
    type MemoryKinds: ::std::fmt::Debug + MayLeak + Eq + 'static;

    /// Tag tracked alongside every pointer. This is used to implement "Stacked Borrows"
    /// <https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html>.
    /// The `default()` is used for pointers to consts, statics, vtables and functions.
    type PointerTag: ::std::fmt::Debug + Copy + Eq + Hash + 'static;

    /// Extra data stored in every call frame.
    type FrameExtra;

    /// Extra data stored in memory. A reference to this is available when `AllocExtra`
    /// gets initialized, so you can e.g., have an `Rc` here if there is global state you
    /// need access to in the `AllocExtra` hooks.
    type MemoryExtra: Default;

    /// Extra data stored in every allocation.
    type AllocExtra: AllocationExtra<Self::PointerTag> + 'static;

    /// Memory's allocation map
    type MemoryMap:
        AllocMap<
            AllocId,
            (MemoryKind<Self::MemoryKinds>, Allocation<Self::PointerTag, Self::AllocExtra>)
        > +
        Default +
        Clone;

    /// The memory kind to use for copied statics -- or None if statics should not be mutated
    /// and thus any such attempt will cause a `ModifiedStatic` error to be raised.
    /// Statics are copied under two circumstances: When they are mutated, and when
    /// `tag_allocation` or `find_foreign_static` (see below) returns an owned allocation
    /// that is added to the memory so that the work is not done twice.
    const STATIC_KIND: Option<Self::MemoryKinds>;

    /// Whether to enforce the validity invariant
    fn enforce_validity(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;

    /// Called before a basic block terminator is executed.
    /// You can use this to detect endlessly running programs.
    fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx>;

    /// Entry point to all function calls.
    ///
    /// Returns either the mir to use for the call, or `None` if execution should
    /// just proceed (which usually means this hook did all the work that the
    /// called function should usually have done). In the latter case, it is
    /// this hook's responsibility to call `goto_block(ret)` to advance the instruction pointer!
    /// (This is to support functions like `__rust_maybe_catch_panic` that neither find a MIR
    /// nor just jump to `ret`, but instead push their own stack frame.)
    /// Passing `dest`and `ret` in the same `Option` proved very annoying when only one of them
    /// was used.
    fn find_fn(
        ecx: &mut InterpCx<'mir, 'tcx, Self>,
        instance: ty::Instance<'tcx>,
        args: &[OpTy<'tcx, Self::PointerTag>],
        dest: Option<PlaceTy<'tcx, Self::PointerTag>>,
        ret: Option<mir::BasicBlock>,
    ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>>;

    /// Directly process an intrinsic without pushing a stack frame.
    /// If this returns successfully, the engine will take care of jumping to the next block.
    fn call_intrinsic(
        ecx: &mut InterpCx<'mir, 'tcx, Self>,
        instance: ty::Instance<'tcx>,
        args: &[OpTy<'tcx, Self::PointerTag>],
        dest: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx>;

    /// Called for read access to a foreign static item.
    ///
    /// This will only be called once per static and machine; the result is cached in
    /// the machine memory. (This relies on `AllocMap::get_or` being able to add the
    /// owned allocation to the map even when the map is shared.)
    ///
    /// This allocation will then be fed to `tag_allocation` to initialize the "extra" state.
    fn find_foreign_static(
        tcx: TyCtxt<'tcx>,
        def_id: DefId,
    ) -> InterpResult<'tcx, Cow<'tcx, Allocation>>;

    /// Called for all binary operations on integer(-like) types when one operand is a pointer
    /// value, and for the `Offset` operation that is inherently about pointers.
    ///
    /// Returns a (value, overflowed) pair if the operation succeeded
    fn ptr_op(
        ecx: &InterpCx<'mir, 'tcx, Self>,
        bin_op: mir::BinOp,
        left: ImmTy<'tcx, Self::PointerTag>,
        right: ImmTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx, (Scalar<Self::PointerTag>, bool)>;

    /// Heap allocations via the `box` keyword.
    fn box_alloc(
        ecx: &mut InterpCx<'mir, 'tcx, Self>,
        dest: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx>;

    /// Called to initialize the "extra" state of an allocation and make the pointers
    /// it contains (in relocations) tagged.  The way we construct allocations is
    /// to always first construct it without extra and then add the extra.
    /// This keeps uniform code paths for handling both allocations created by CTFE
    /// for statics, and allocations ceated by Miri during evaluation.
    ///
    /// `kind` is the kind of the allocation being tagged; it can be `None` when
    /// it's a static and `STATIC_KIND` is `None`.
    ///
    /// This should avoid copying if no work has to be done! If this returns an owned
    /// allocation (because a copy had to be done to add tags or metadata), machine memory will
    /// cache the result. (This relies on `AllocMap::get_or` being able to add the
    /// owned allocation to the map even when the map is shared.)
    ///
    /// For static allocations, the tag returned must be the same as the one returned by
    /// `tag_static_base_pointer`.
    fn tag_allocation<'b>(
        memory_extra: &Self::MemoryExtra,
        id: AllocId,
        alloc: Cow<'b, Allocation>,
        kind: Option<MemoryKind<Self::MemoryKinds>>,
    ) -> (Cow<'b, Allocation<Self::PointerTag, Self::AllocExtra>>, Self::PointerTag);

    /// Return the "base" tag for the given static allocation: the one that is used for direct
    /// accesses to this static/const/fn allocation.
    ///
    /// Be aware that requesting the `Allocation` for that `id` will lead to cycles
    /// for cyclic statics!
    fn tag_static_base_pointer(
        memory_extra: &Self::MemoryExtra,
        id: AllocId,
    ) -> Self::PointerTag;

    /// Executes a retagging operation
    #[inline]
    fn retag(
        _ecx: &mut InterpCx<'mir, 'tcx, Self>,
        _kind: mir::RetagKind,
        _place: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx> {
        Ok(())
    }

    /// Called immediately before a new stack frame got pushed
    fn stack_push(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx, Self::FrameExtra>;

    /// Called immediately after a stack frame gets popped
    fn stack_pop(
        ecx: &mut InterpCx<'mir, 'tcx, Self>,
        extra: Self::FrameExtra,
    ) -> InterpResult<'tcx>;

    fn int_to_ptr(
        _mem: &Memory<'mir, 'tcx, Self>,
        int: u64,
    ) -> InterpResult<'tcx, Pointer<Self::PointerTag>> {
        if int == 0 {
            err!(InvalidNullPointerUsage)
        } else {
            err!(ReadBytesAsPointer)
        }
    }

    fn ptr_to_int(
        _mem: &Memory<'mir, 'tcx, Self>,
        _ptr: Pointer<Self::PointerTag>,
    ) -> InterpResult<'tcx, u64> {
        err!(ReadPointerAsBytes)
    }
}
<commit_msg>more inlining<commit_after>//! This module contains everything needed to instantiate an interpreter.
//! This separation exists to ensure that no fancy miri features like
//! interpreting common C functions leak into CTFE.

use std::borrow::{Borrow, Cow};
use std::hash::Hash;

use rustc::hir::def_id::DefId;
use rustc::mir;
use rustc::ty::{self, TyCtxt};

use super::{
    Allocation, AllocId, InterpResult, InterpError, Scalar, AllocationExtra,
    InterpCx, PlaceTy, OpTy, ImmTy, MemoryKind, Pointer, Memory,
};

/// Whether this kind of memory is allowed to leak
pub trait MayLeak: Copy {
    fn may_leak(self) -> bool;
}

/// The functionality needed by memory to manage its allocations
pub trait AllocMap<K: Hash + Eq, V> {
    /// Tests if the map contains the given key.
    /// Deliberately takes `&mut` because that is sufficient, and some implementations
    /// can be more efficient then (using `RefCell::get_mut`).
    fn contains_key<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> bool
        where K: Borrow<Q>;

    /// Inserts a new entry into the map.
    fn insert(&mut self, k: K, v: V) -> Option<V>;

    /// Removes an entry from the map.
    fn remove<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> Option<V>
        where K: Borrow<Q>;

    /// Returns data based the keys and values in the map.
    fn filter_map_collect<T>(&self, f: impl FnMut(&K, &V) -> Option<T>) -> Vec<T>;

    /// Returns a reference to entry `k`. If no such entry exists, call
    /// `vacant` and either forward its error, or add its result to the map
    /// and return a reference to *that*.
    fn get_or<E>(
        &self,
        k: K,
        vacant: impl FnOnce() -> Result<V, E>
    ) -> Result<&V, E>;

    /// Returns a mutable reference to entry `k`. If no such entry exists, call
    /// `vacant` and either forward its error, or add its result to the map
    /// and return a reference to *that*.
    fn get_mut_or<E>(
        &mut self,
        k: K,
        vacant: impl FnOnce() -> Result<V, E>
    ) -> Result<&mut V, E>;
}

/// Methods of this trait signifies a point where CTFE evaluation would fail
/// and some use case dependent behaviour can instead be applied.
pub trait Machine<'mir, 'tcx>: Sized {
    /// Additional memory kinds a machine wishes to distinguish from the builtin ones
    type MemoryKinds: ::std::fmt::Debug + MayLeak + Eq + 'static;

    /// Tag tracked alongside every pointer. This is used to implement "Stacked Borrows"
    /// <https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html>.
    /// The `default()` is used for pointers to consts, statics, vtables and functions.
    type PointerTag: ::std::fmt::Debug + Copy + Eq + Hash + 'static;

    /// Extra data stored in every call frame.
    type FrameExtra;

    /// Extra data stored in memory. A reference to this is available when `AllocExtra`
    /// gets initialized, so you can e.g., have an `Rc` here if there is global state you
    /// need access to in the `AllocExtra` hooks.
    type MemoryExtra: Default;

    /// Extra data stored in every allocation.
    type AllocExtra: AllocationExtra<Self::PointerTag> + 'static;

    /// Memory's allocation map
    type MemoryMap:
        AllocMap<
            AllocId,
            (MemoryKind<Self::MemoryKinds>, Allocation<Self::PointerTag, Self::AllocExtra>)
        > +
        Default +
        Clone;

    /// The memory kind to use for copied statics -- or None if statics should not be mutated
    /// and thus any such attempt will cause a `ModifiedStatic` error to be raised.
    /// Statics are copied under two circumstances: When they are mutated, and when
    /// `tag_allocation` or `find_foreign_static` (see below) returns an owned allocation
    /// that is added to the memory so that the work is not done twice.
    const STATIC_KIND: Option<Self::MemoryKinds>;

    /// Whether to enforce the validity invariant
    fn enforce_validity(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;

    /// Called before a basic block terminator is executed.
    /// You can use this to detect endlessly running programs.
    fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx>;

    /// Entry point to all function calls.
    ///
    /// Returns either the mir to use for the call, or `None` if execution should
    /// just proceed (which usually means this hook did all the work that the
    /// called function should usually have done). In the latter case, it is
    /// this hook's responsibility to call `goto_block(ret)` to advance the instruction pointer!
    /// (This is to support functions like `__rust_maybe_catch_panic` that neither find a MIR
    /// nor just jump to `ret`, but instead push their own stack frame.)
    /// Passing `dest`and `ret` in the same `Option` proved very annoying when only one of them
    /// was used.
    fn find_fn(
        ecx: &mut InterpCx<'mir, 'tcx, Self>,
        instance: ty::Instance<'tcx>,
        args: &[OpTy<'tcx, Self::PointerTag>],
        dest: Option<PlaceTy<'tcx, Self::PointerTag>>,
        ret: Option<mir::BasicBlock>,
    ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>>;

    /// Directly process an intrinsic without pushing a stack frame.
    /// If this returns successfully, the engine will take care of jumping to the next block.
    fn call_intrinsic(
        ecx: &mut InterpCx<'mir, 'tcx, Self>,
        instance: ty::Instance<'tcx>,
        args: &[OpTy<'tcx, Self::PointerTag>],
        dest: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx>;

    /// Called for read access to a foreign static item.
    ///
    /// This will only be called once per static and machine; the result is cached in
    /// the machine memory. (This relies on `AllocMap::get_or` being able to add the
    /// owned allocation to the map even when the map is shared.)
    ///
    /// This allocation will then be fed to `tag_allocation` to initialize the "extra" state.
    fn find_foreign_static(
        tcx: TyCtxt<'tcx>,
        def_id: DefId,
    ) -> InterpResult<'tcx, Cow<'tcx, Allocation>>;

    /// Called for all binary operations on integer(-like) types when one operand is a pointer
    /// value, and for the `Offset` operation that is inherently about pointers.
    ///
    /// Returns a (value, overflowed) pair if the operation succeeded
    fn ptr_op(
        ecx: &InterpCx<'mir, 'tcx, Self>,
        bin_op: mir::BinOp,
        left: ImmTy<'tcx, Self::PointerTag>,
        right: ImmTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx, (Scalar<Self::PointerTag>, bool)>;

    /// Heap allocations via the `box` keyword.
    fn box_alloc(
        ecx: &mut InterpCx<'mir, 'tcx, Self>,
        dest: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx>;

    /// Called to initialize the "extra" state of an allocation and make the pointers
    /// it contains (in relocations) tagged.  The way we construct allocations is
    /// to always first construct it without extra and then add the extra.
    /// This keeps uniform code paths for handling both allocations created by CTFE
    /// for statics, and allocations ceated by Miri during evaluation.
    ///
    /// `kind` is the kind of the allocation being tagged; it can be `None` when
    /// it's a static and `STATIC_KIND` is `None`.
    ///
    /// This should avoid copying if no work has to be done! If this returns an owned
    /// allocation (because a copy had to be done to add tags or metadata), machine memory will
    /// cache the result. (This relies on `AllocMap::get_or` being able to add the
    /// owned allocation to the map even when the map is shared.)
    ///
    /// For static allocations, the tag returned must be the same as the one returned by
    /// `tag_static_base_pointer`.
    fn tag_allocation<'b>(
        memory_extra: &Self::MemoryExtra,
        id: AllocId,
        alloc: Cow<'b, Allocation>,
        kind: Option<MemoryKind<Self::MemoryKinds>>,
    ) -> (Cow<'b, Allocation<Self::PointerTag, Self::AllocExtra>>, Self::PointerTag);

    /// Return the "base" tag for the given static allocation: the one that is used for direct
    /// accesses to this static/const/fn allocation.
    ///
    /// Be aware that requesting the `Allocation` for that `id` will lead to cycles
    /// for cyclic statics!
    fn tag_static_base_pointer(
        memory_extra: &Self::MemoryExtra,
        id: AllocId,
    ) -> Self::PointerTag;

    /// Executes a retagging operation
    #[inline]
    fn retag(
        _ecx: &mut InterpCx<'mir, 'tcx, Self>,
        _kind: mir::RetagKind,
        _place: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx> {
        Ok(())
    }

    /// Called immediately before a new stack frame got pushed
    fn stack_push(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx, Self::FrameExtra>;

    /// Called immediately after a stack frame gets popped
    fn stack_pop(
        ecx: &mut InterpCx<'mir, 'tcx, Self>,
        extra: Self::FrameExtra,
    ) -> InterpResult<'tcx>;

    #[inline(always)]
    fn int_to_ptr(
        _mem: &Memory<'mir, 'tcx, Self>,
        int: u64,
    ) -> InterpResult<'tcx, Pointer<Self::PointerTag>> {
        Err((if int == 0 {
            InterpError::InvalidNullPointerUsage
        } else {
            InterpError::ReadBytesAsPointer
        }).into())
    }

    #[inline(always)]
    fn ptr_to_int(
        _mem: &Memory<'mir, 'tcx, Self>,
        _ptr: Pointer<Self::PointerTag>,
    ) -> InterpResult<'tcx, u64> {
        err!(ReadPointerAsBytes)
    }
}
<|endoftext|>


<commit_before>//! This module contains everything needed to instantiate an interpreter.
//! This separation exists to ensure that no fancy miri features like
//! interpreting common C functions leak into CTFE.

use std::borrow::{Borrow, Cow};
use std::hash::Hash;

use rustc::hir::def_id::DefId;
use rustc::mir;
use rustc::ty::{self, query::TyCtxtAt};

use super::{
    Allocation, AllocId, InterpResult, Scalar, AllocationExtra,
    InterpretCx, PlaceTy, OpTy, ImmTy, MemoryKind, Pointer,
    InterpErrorInfo, InterpError
};

/// Whether this kind of memory is allowed to leak
pub trait MayLeak: Copy {
    fn may_leak(self) -> bool;
}

/// The functionality needed by memory to manage its allocations
pub trait AllocMap<K: Hash + Eq, V> {
    /// Tests if the map contains the given key.
    /// Deliberately takes `&mut` because that is sufficient, and some implementations
    /// can be more efficient then (using `RefCell::get_mut`).
    fn contains_key<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> bool
        where K: Borrow<Q>;

    /// Inserts a new entry into the map.
    fn insert(&mut self, k: K, v: V) -> Option<V>;

    /// Removes an entry from the map.
    fn remove<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> Option<V>
        where K: Borrow<Q>;

    /// Returns data based the keys and values in the map.
    fn filter_map_collect<T>(&self, f: impl FnMut(&K, &V) -> Option<T>) -> Vec<T>;

    /// Returns a reference to entry `k`. If no such entry exists, call
    /// `vacant` and either forward its error, or add its result to the map
    /// and return a reference to *that*.
    fn get_or<E>(
        &self,
        k: K,
        vacant: impl FnOnce() -> Result<V, E>
    ) -> Result<&V, E>;

    /// Returns a mutable reference to entry `k`. If no such entry exists, call
    /// `vacant` and either forward its error, or add its result to the map
    /// and return a reference to *that*.
    fn get_mut_or<E>(
        &mut self,
        k: K,
        vacant: impl FnOnce() -> Result<V, E>
    ) -> Result<&mut V, E>;
}

/// Methods of this trait signifies a point where CTFE evaluation would fail
/// and some use case dependent behaviour can instead be applied.
pub trait Machine<'mir, 'tcx>: Sized {
    /// Additional memory kinds a machine wishes to distinguish from the builtin ones
    type MemoryKinds: ::std::fmt::Debug + MayLeak + Eq + 'static;

    /// Tag tracked alongside every pointer. This is used to implement "Stacked Borrows"
    /// <https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html>.
    /// The `default()` is used for pointers to consts, statics, vtables and functions.
    type PointerTag: ::std::fmt::Debug + Copy + Eq + Hash + 'static;

    /// Extra data stored in every call frame.
    type FrameExtra;

    /// Extra data stored in memory. A reference to this is available when `AllocExtra`
    /// gets initialized, so you can e.g., have an `Rc` here if there is global state you
    /// need access to in the `AllocExtra` hooks.
    type MemoryExtra: Default;

    /// Extra data stored in every allocation.
    type AllocExtra: AllocationExtra<Self::PointerTag> + 'static;

    /// Memory's allocation map
    type MemoryMap:
        AllocMap<
            AllocId,
            (MemoryKind<Self::MemoryKinds>, Allocation<Self::PointerTag, Self::AllocExtra>)
        > +
        Default +
        Clone;

    /// The memory kind to use for copied statics -- or None if statics should not be mutated
    /// and thus any such attempt will cause a `ModifiedStatic` error to be raised.
    /// Statics are copied under two circumstances: When they are mutated, and when
    /// `tag_allocation` or `find_foreign_static` (see below) returns an owned allocation
    /// that is added to the memory so that the work is not done twice.
    const STATIC_KIND: Option<Self::MemoryKinds>;

    /// Whether to enforce the validity invariant
    fn enforce_validity(ecx: &InterpretCx<'mir, 'tcx, Self>) -> bool;

    /// Called before a basic block terminator is executed.
    /// You can use this to detect endlessly running programs.
    fn before_terminator(ecx: &mut InterpretCx<'mir, 'tcx, Self>) -> InterpResult<'tcx>;

    /// Entry point to all function calls.
    ///
    /// Returns either the mir to use for the call, or `None` if execution should
    /// just proceed (which usually means this hook did all the work that the
    /// called function should usually have done). In the latter case, it is
    /// this hook's responsibility to call `goto_block(ret)` to advance the instruction pointer!
    /// (This is to support functions like `__rust_maybe_catch_panic` that neither find a MIR
    /// nor just jump to `ret`, but instead push their own stack frame.)
    /// Passing `dest`and `ret` in the same `Option` proved very annoying when only one of them
    /// was used.
    fn find_fn(
        ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        instance: ty::Instance<'tcx>,
        args: &[OpTy<'tcx, Self::PointerTag>],
        dest: Option<PlaceTy<'tcx, Self::PointerTag>>,
        ret: Option<mir::BasicBlock>,
    ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>>;

    /// Directly process an intrinsic without pushing a stack frame.
    /// If this returns successfully, the engine will take care of jumping to the next block.
    fn call_intrinsic(
        ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        instance: ty::Instance<'tcx>,
        args: &[OpTy<'tcx, Self::PointerTag>],
        dest: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx>;

    /// Called for read access to a foreign static item.
    ///
    /// This will only be called once per static and machine; the result is cached in
    /// the machine memory. (This relies on `AllocMap::get_or` being able to add the
    /// owned allocation to the map even when the map is shared.)
    ///
    /// This allocation will then be fed to `tag_allocation` to initialize the "extra" state.
    fn find_foreign_static(
        def_id: DefId,
        tcx: TyCtxtAt<'tcx>,
    ) -> InterpResult<'tcx, Cow<'tcx, Allocation>>;

    /// Called for all binary operations on integer(-like) types when one operand is a pointer
    /// value, and for the `Offset` operation that is inherently about pointers.
    ///
    /// Returns a (value, overflowed) pair if the operation succeeded
    fn ptr_op(
        ecx: &InterpretCx<'mir, 'tcx, Self>,
        bin_op: mir::BinOp,
        left: ImmTy<'tcx, Self::PointerTag>,
        right: ImmTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx, (Scalar<Self::PointerTag>, bool)>;

    /// Heap allocations via the `box` keyword.
    fn box_alloc(
        ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        dest: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx>;

    /// Called to initialize the "extra" state of an allocation and make the pointers
    /// it contains (in relocations) tagged.  The way we construct allocations is
    /// to always first construct it without extra and then add the extra.
    /// This keeps uniform code paths for handling both allocations created by CTFE
    /// for statics, and allocations ceated by Miri during evaluation.
    ///
    /// `kind` is the kind of the allocation being tagged; it can be `None` when
    /// it's a static and `STATIC_KIND` is `None`.
    ///
    /// This should avoid copying if no work has to be done! If this returns an owned
    /// allocation (because a copy had to be done to add tags or metadata), machine memory will
    /// cache the result. (This relies on `AllocMap::get_or` being able to add the
    /// owned allocation to the map even when the map is shared.)
    ///
    /// For static allocations, the tag returned must be the same as the one returned by
    /// `tag_static_base_pointer`.
    fn tag_allocation<'b>(
        id: AllocId,
        alloc: Cow<'b, Allocation>,
        kind: Option<MemoryKind<Self::MemoryKinds>>,
        memory_extra: &Self::MemoryExtra,
    ) -> (Cow<'b, Allocation<Self::PointerTag, Self::AllocExtra>>, Self::PointerTag);

    /// Return the "base" tag for the given static allocation: the one that is used for direct
    /// accesses to this static/const/fn allocation.
    ///
    /// Be aware that requesting the `Allocation` for that `id` will lead to cycles
    /// for cyclic statics!
    fn tag_static_base_pointer(
        id: AllocId,
        memory_extra: &Self::MemoryExtra,
    ) -> Self::PointerTag;

    /// Executes a retagging operation
    #[inline]
    fn retag(
        _ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        _kind: mir::RetagKind,
        _place: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx> {
        Ok(())
    }

    /// Called immediately before a new stack frame got pushed
    fn stack_push(ecx: &mut InterpretCx<'mir, 'tcx, Self>) -> InterpResult<'tcx, Self::FrameExtra>;

    /// Called immediately after a stack frame gets popped
    fn stack_pop(
        ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        extra: Self::FrameExtra,
    ) -> InterpResult<'tcx>;

    fn int_to_ptr(
        _int: u64,
        _extra: &Self::MemoryExtra,
    ) -> InterpResult<'tcx, Pointer<Self::PointerTag>> {
        Err(InterpErrorInfo::from(InterpError::ReadBytesAsPointer))
    }

    fn ptr_to_int(
        _ptr: Pointer<Self::PointerTag>,
        _extra: &Self::MemoryExtra,
    ) -> InterpResult<'tcx, u64> {
        Err(InterpErrorInfo::from(InterpError::ReadPointerAsBytes))
    }
}
<commit_msg>Add special behaviour when int is zero<commit_after>//! This module contains everything needed to instantiate an interpreter.
//! This separation exists to ensure that no fancy miri features like
//! interpreting common C functions leak into CTFE.

use std::borrow::{Borrow, Cow};
use std::hash::Hash;

use rustc::hir::def_id::DefId;
use rustc::mir;
use rustc::ty::{self, query::TyCtxtAt};

use super::{
    Allocation, AllocId, InterpResult, Scalar, AllocationExtra,
    InterpretCx, PlaceTy, OpTy, ImmTy, MemoryKind, Pointer,
    InterpErrorInfo, InterpError
};

/// Whether this kind of memory is allowed to leak
pub trait MayLeak: Copy {
    fn may_leak(self) -> bool;
}

/// The functionality needed by memory to manage its allocations
pub trait AllocMap<K: Hash + Eq, V> {
    /// Tests if the map contains the given key.
    /// Deliberately takes `&mut` because that is sufficient, and some implementations
    /// can be more efficient then (using `RefCell::get_mut`).
    fn contains_key<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> bool
        where K: Borrow<Q>;

    /// Inserts a new entry into the map.
    fn insert(&mut self, k: K, v: V) -> Option<V>;

    /// Removes an entry from the map.
    fn remove<Q: ?Sized + Hash + Eq>(&mut self, k: &Q) -> Option<V>
        where K: Borrow<Q>;

    /// Returns data based the keys and values in the map.
    fn filter_map_collect<T>(&self, f: impl FnMut(&K, &V) -> Option<T>) -> Vec<T>;

    /// Returns a reference to entry `k`. If no such entry exists, call
    /// `vacant` and either forward its error, or add its result to the map
    /// and return a reference to *that*.
    fn get_or<E>(
        &self,
        k: K,
        vacant: impl FnOnce() -> Result<V, E>
    ) -> Result<&V, E>;

    /// Returns a mutable reference to entry `k`. If no such entry exists, call
    /// `vacant` and either forward its error, or add its result to the map
    /// and return a reference to *that*.
    fn get_mut_or<E>(
        &mut self,
        k: K,
        vacant: impl FnOnce() -> Result<V, E>
    ) -> Result<&mut V, E>;
}

/// Methods of this trait signifies a point where CTFE evaluation would fail
/// and some use case dependent behaviour can instead be applied.
pub trait Machine<'mir, 'tcx>: Sized {
    /// Additional memory kinds a machine wishes to distinguish from the builtin ones
    type MemoryKinds: ::std::fmt::Debug + MayLeak + Eq + 'static;

    /// Tag tracked alongside every pointer. This is used to implement "Stacked Borrows"
    /// <https://www.ralfj.de/blog/2018/08/07/stacked-borrows.html>.
    /// The `default()` is used for pointers to consts, statics, vtables and functions.
    type PointerTag: ::std::fmt::Debug + Copy + Eq + Hash + 'static;

    /// Extra data stored in every call frame.
    type FrameExtra;

    /// Extra data stored in memory. A reference to this is available when `AllocExtra`
    /// gets initialized, so you can e.g., have an `Rc` here if there is global state you
    /// need access to in the `AllocExtra` hooks.
    type MemoryExtra: Default;

    /// Extra data stored in every allocation.
    type AllocExtra: AllocationExtra<Self::PointerTag> + 'static;

    /// Memory's allocation map
    type MemoryMap:
        AllocMap<
            AllocId,
            (MemoryKind<Self::MemoryKinds>, Allocation<Self::PointerTag, Self::AllocExtra>)
        > +
        Default +
        Clone;

    /// The memory kind to use for copied statics -- or None if statics should not be mutated
    /// and thus any such attempt will cause a `ModifiedStatic` error to be raised.
    /// Statics are copied under two circumstances: When they are mutated, and when
    /// `tag_allocation` or `find_foreign_static` (see below) returns an owned allocation
    /// that is added to the memory so that the work is not done twice.
    const STATIC_KIND: Option<Self::MemoryKinds>;

    /// Whether to enforce the validity invariant
    fn enforce_validity(ecx: &InterpretCx<'mir, 'tcx, Self>) -> bool;

    /// Called before a basic block terminator is executed.
    /// You can use this to detect endlessly running programs.
    fn before_terminator(ecx: &mut InterpretCx<'mir, 'tcx, Self>) -> InterpResult<'tcx>;

    /// Entry point to all function calls.
    ///
    /// Returns either the mir to use for the call, or `None` if execution should
    /// just proceed (which usually means this hook did all the work that the
    /// called function should usually have done). In the latter case, it is
    /// this hook's responsibility to call `goto_block(ret)` to advance the instruction pointer!
    /// (This is to support functions like `__rust_maybe_catch_panic` that neither find a MIR
    /// nor just jump to `ret`, but instead push their own stack frame.)
    /// Passing `dest`and `ret` in the same `Option` proved very annoying when only one of them
    /// was used.
    fn find_fn(
        ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        instance: ty::Instance<'tcx>,
        args: &[OpTy<'tcx, Self::PointerTag>],
        dest: Option<PlaceTy<'tcx, Self::PointerTag>>,
        ret: Option<mir::BasicBlock>,
    ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>>;

    /// Directly process an intrinsic without pushing a stack frame.
    /// If this returns successfully, the engine will take care of jumping to the next block.
    fn call_intrinsic(
        ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        instance: ty::Instance<'tcx>,
        args: &[OpTy<'tcx, Self::PointerTag>],
        dest: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx>;

    /// Called for read access to a foreign static item.
    ///
    /// This will only be called once per static and machine; the result is cached in
    /// the machine memory. (This relies on `AllocMap::get_or` being able to add the
    /// owned allocation to the map even when the map is shared.)
    ///
    /// This allocation will then be fed to `tag_allocation` to initialize the "extra" state.
    fn find_foreign_static(
        def_id: DefId,
        tcx: TyCtxtAt<'tcx>,
    ) -> InterpResult<'tcx, Cow<'tcx, Allocation>>;

    /// Called for all binary operations on integer(-like) types when one operand is a pointer
    /// value, and for the `Offset` operation that is inherently about pointers.
    ///
    /// Returns a (value, overflowed) pair if the operation succeeded
    fn ptr_op(
        ecx: &InterpretCx<'mir, 'tcx, Self>,
        bin_op: mir::BinOp,
        left: ImmTy<'tcx, Self::PointerTag>,
        right: ImmTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx, (Scalar<Self::PointerTag>, bool)>;

    /// Heap allocations via the `box` keyword.
    fn box_alloc(
        ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        dest: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx>;

    /// Called to initialize the "extra" state of an allocation and make the pointers
    /// it contains (in relocations) tagged.  The way we construct allocations is
    /// to always first construct it without extra and then add the extra.
    /// This keeps uniform code paths for handling both allocations created by CTFE
    /// for statics, and allocations ceated by Miri during evaluation.
    ///
    /// `kind` is the kind of the allocation being tagged; it can be `None` when
    /// it's a static and `STATIC_KIND` is `None`.
    ///
    /// This should avoid copying if no work has to be done! If this returns an owned
    /// allocation (because a copy had to be done to add tags or metadata), machine memory will
    /// cache the result. (This relies on `AllocMap::get_or` being able to add the
    /// owned allocation to the map even when the map is shared.)
    ///
    /// For static allocations, the tag returned must be the same as the one returned by
    /// `tag_static_base_pointer`.
    fn tag_allocation<'b>(
        id: AllocId,
        alloc: Cow<'b, Allocation>,
        kind: Option<MemoryKind<Self::MemoryKinds>>,
        memory_extra: &Self::MemoryExtra,
    ) -> (Cow<'b, Allocation<Self::PointerTag, Self::AllocExtra>>, Self::PointerTag);

    /// Return the "base" tag for the given static allocation: the one that is used for direct
    /// accesses to this static/const/fn allocation.
    ///
    /// Be aware that requesting the `Allocation` for that `id` will lead to cycles
    /// for cyclic statics!
    fn tag_static_base_pointer(
        id: AllocId,
        memory_extra: &Self::MemoryExtra,
    ) -> Self::PointerTag;

    /// Executes a retagging operation
    #[inline]
    fn retag(
        _ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        _kind: mir::RetagKind,
        _place: PlaceTy<'tcx, Self::PointerTag>,
    ) -> InterpResult<'tcx> {
        Ok(())
    }

    /// Called immediately before a new stack frame got pushed
    fn stack_push(ecx: &mut InterpretCx<'mir, 'tcx, Self>) -> InterpResult<'tcx, Self::FrameExtra>;

    /// Called immediately after a stack frame gets popped
    fn stack_pop(
        ecx: &mut InterpretCx<'mir, 'tcx, Self>,
        extra: Self::FrameExtra,
    ) -> InterpResult<'tcx>;

    fn int_to_ptr(
        int: u64,
        _extra: &Self::MemoryExtra,
    ) -> InterpResult<'tcx, Pointer<Self::PointerTag>> {
        if int == 0 {
            Err(InterpErrorInfo::from(InterpError::InvalidNullPointerUsage))
        } else {
            Err(InterpErrorInfo::from(InterpError::ReadBytesAsPointer))
        }
    }

    fn ptr_to_int(
        _ptr: Pointer<Self::PointerTag>,
        _extra: &Self::MemoryExtra,
    ) -> InterpResult<'tcx, u64> {
        Err(InterpErrorInfo::from(InterpError::ReadPointerAsBytes))
    }
}
<|endoftext|>


<commit_before>use serde::{Deserialize, Serialize};
use serde_json::Result;
use std::path::Path;
use std::process::exit;
use std::{env, fs};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Measurement {
    command: String,
    mean: f64,
    stddev: f64,
    median: f64,
    user: f64,
    system: f64,
    min: f64,
    max: f64,
    times: Vec<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Measurements {
    results: Vec<Measurement>,
}

#[derive(Debug, Clone, PartialEq)]
struct Regression {
    threshold: f64,
    difference: f64,
    baseline: Measurement,
    latest: Measurement,
}

#[derive(Debug, Clone, PartialEq)]
struct MeasurementGroup {
    version: String,
    run: String,
    measurement: Measurement,
}

fn regression(baseline: &Measurement, latest: &Measurement) -> Option<Regression> {
    let threshold = 1.05; // 5% regression threshold
    let difference = latest.median / baseline.median;
    if difference > threshold {
        Some(Regression {
            threshold,
            difference,
            baseline: baseline.clone(),
            latest: latest.clone(),
        })
    } else {
        None
    }
}

fn measurements_from_files(
    results_directory: &Path,
) -> std::result::Result<Vec<Measurements>, std::io::Error> {
    let result_files = fs::read_dir(results_directory)?;

    result_files
        .into_iter()
        .map(|f| f.unwrap().path())
        .filter(|filename| {
            filename
                .extension()
                .and_then(|ext| ext.to_str())
                .map_or(false, |ext| ext.ends_with("json"))
        })
        .map(|filename| {
            println!("{:?}", filename);
            let contents = fs::read_to_string(&filename);
            contents.map(|c| serde_json::from_str::<Measurements>(&c).unwrap()) //TODO rm unwrap!
        })
        .collect()
}

fn detect_regressions(measurements: Vec<Measurements>) -> Vec<Regression> {
    panic!()
}

fn main() {
    // TODO args lib
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("please provide the results directory");
        exit(1);
    }
    let results_directory = &args[1];

    let foo = measurements_from_files(Path::new(&results_directory));
    foo.map(|x| detect_regressions(x));

    // let measurements: Result<Vec<(&str, Vec<MeasurementGroup>)>> = result_files
    //     .into_iter()
    //     .map(|f| f.unwrap().path())
    //     .filter(|filename| {
    //         filename
    //             .extension()
    //             .and_then(|ext| ext.to_str())
    //             .map_or(false, |ext| ext.ends_with("json"))
    //     })
    //     .map(|filename| {
    //         println!("{:?}", filename);
    //         let contents = fs::read_to_string(&filename).unwrap();
    //         let measurements: Result<Measurements> = serde_json::from_str(&contents);
    //         let results = measurements.map(|x| x.results[0].clone());
    //         let filepath = filename.into_os_string().into_string().unwrap();
    //         let parts: Vec<&str> = filepath.split("_").collect();

    //         // the way we're running these, the files will each contain exactly one measurement
    //         results.map(|r| MeasurementGroup {
    //             version: parts[0].to_owned(),
    //             run: parts[1..].join(""),
    //             measurement: r.clone(),
    //         })
    //     })
    //     .collect();

    // TODO
    // - group by project and metric
    // - each group will have 2 measurements for each branch
    // - perform regressions on each pair
    // latest_parse_02_mini_project_dont_merge.json
    // latest_parse_01_mini_project_dont_merge.json
    // baseline_parse_02_mini_project_dont_merge.json
    // baseline_parse_01_mini_project_dont_merge.json
    // command, project -> 2 version
    // let x: Vec<(&str, Vec<MeasurementGroup>)>;

    // TODO exit(1) when Regression is present
    // match foo {
    //     Err(&e) => panic!("{}", &e),
    //     Ok(_) => (),
    // }
    foo.clone().map(|f| println!("{:?}", f)).unwrap();
}
<commit_msg>still wip but compiles<commit_after>use serde::{Deserialize, Serialize};
use serde_json::Result;
use std::path::Path;
use std::process::exit;
use std::{env, fs};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Measurement {
    command: String,
    mean: f64,
    stddev: f64,
    median: f64,
    user: f64,
    system: f64,
    min: f64,
    max: f64,
    times: Vec<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Measurements {
    results: Vec<Measurement>,
}

#[derive(Debug, Clone, PartialEq)]
struct Regression {
    threshold: f64,
    difference: f64,
    baseline: Measurement,
    latest: Measurement,
}

#[derive(Debug, Clone, PartialEq)]
struct MeasurementGroup {
    version: String,
    run: String,
    measurement: Measurement,
}

fn regression(baseline: &Measurement, latest: &Measurement) -> Option<Regression> {
    let threshold = 1.05; // 5% regression threshold
    let difference = latest.median / baseline.median;
    if difference > threshold {
        Some(Regression {
            threshold,
            difference,
            baseline: baseline.clone(),
            latest: latest.clone(),
        })
    } else {
        None
    }
}

fn measurements_from_files(
    results_directory: &Path,
) -> std::result::Result<Vec<Measurements>, std::io::Error> {
    let result_files = fs::read_dir(results_directory)?;

    result_files
        .into_iter()
        .map(|f| f.unwrap().path())
        .filter(|filename| {
            filename
                .extension()
                .and_then(|ext| ext.to_str())
                .map_or(false, |ext| ext.ends_with("json"))
        })
        .map(|filename| {
            println!("{:?}", filename);
            let contents = fs::read_to_string(&filename);
            contents.map(|c| serde_json::from_str::<Measurements>(&c).unwrap()) //TODO rm unwrap!
        })
        .collect()
}

fn detect_regressions(measurements: Vec<Measurements>) -> Vec<Regression> {
    panic!()
}

fn main() {
    // TODO args lib
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("please provide the results directory");
        exit(1);
    }
    let results_directory = &args[1];

    let measurements = measurements_from_files(Path::new(&results_directory));
    let regressions = measurements.map(|x| detect_regressions(x));

    // let measurements: Result<Vec<(&str, Vec<MeasurementGroup>)>> = result_files
    //     .into_iter()
    //     .map(|f| f.unwrap().path())
    //     .filter(|filename| {
    //         filename
    //             .extension()
    //             .and_then(|ext| ext.to_str())
    //             .map_or(false, |ext| ext.ends_with("json"))
    //     })
    //     .map(|filename| {
    //         println!("{:?}", filename);
    //         let contents = fs::read_to_string(&filename).unwrap();
    //         let measurements: Result<Measurements> = serde_json::from_str(&contents);
    //         let results = measurements.map(|x| x.results[0].clone());
    //         let filepath = filename.into_os_string().into_string().unwrap();
    //         let parts: Vec<&str> = filepath.split("_").collect();

    //         // the way we're running these, the files will each contain exactly one measurement
    //         results.map(|r| MeasurementGroup {
    //             version: parts[0].to_owned(),
    //             run: parts[1..].join(""),
    //             measurement: r.clone(),
    //         })
    //     })
    //     .collect();

    // TODO
    // - group by project and metric
    // - each group will have 2 measurements for each branch
    // - perform regressions on each pair
    // latest_parse_02_mini_project_dont_merge.json
    // latest_parse_01_mini_project_dont_merge.json
    // baseline_parse_02_mini_project_dont_merge.json
    // baseline_parse_01_mini_project_dont_merge.json
    // command, project -> 2 version
    // let x: Vec<(&str, Vec<MeasurementGroup>)>;

    // TODO exit(1) when Regression is present
    // match foo {
    //     Err(&e) => panic!("{}", &e),
    //     Ok(_) => (),
    // }

    // print the regressions or throw
    match regressions {
        Err(e) => panic!("{}", e),
        Ok(rs) => println!("{:?}", rs)
    }
}
<|endoftext|>


<commit_before>use ast::Ast;
use name::*;
use runtime::eval::{
    eval,
    Value::{self, *},
    BIF,
};
use std::rc::Rc;
use ty::Ty;
use util::assoc::Assoc;

use num::BigInt;

#[derive(Debug, Clone, PartialEq)]
pub struct TypedValue {
    pub ty: Ast,
    pub val: Value,
}

pub fn erase_type(tv: &TypedValue) -> Value { tv.val.clone() }
pub fn erase_value(tv: &TypedValue) -> Ty { Ty::new(tv.ty.clone()) }

pub fn core_typed_values() -> Assoc<Name, TypedValue> {
    assoc_n!(
        "fix" =>
        tyf!( { "Type" "forall_type" :
            "param" => ["F"], // has to be a function, but we don't know its arity
            "body" => (import [* [forall "param"]] { "Type" "fn" :
                "param" => [ { "Type" "fn" :
                    "param" => [{"Type" "fn" : "param" => [], "ret" => (vr "F") }],
                    "ret" => (vr "F")} ],
                "ret" => (vr "F") })},
            // TODO: built-in functions, even though none of them make sense here, shouldn't crash
            ( Function(cl) ) => {
                let new_env = cl.env.set(cl.params[0],
                    // reconstruct the invocation that caused this:
                    Function(Rc::new(::runtime::eval::Closure {
                        body: ast!({"Expr" "apply" :
                            "rator" => (vr "fix"),
                            "rand" => [(vr "orig_arg")]}),
                        params: vec![],
                        env: assoc_n!("orig_arg" => Function(cl.clone()),
                                      // TODO: `core_values` does the `map` every time...
                                      "fix" => core_values().find_or_panic(&n("fix")).clone())})));
                eval(&cl.body, new_env).unwrap() // TODO: should be able to return `Result`
            }
        ),
        "plus" =>
        tf!([( "Int", "Int" ) -> "Int"],
             ( Int(a), Int(b) ) => { Int( a.clone() + b ) }),
        "minus" =>
        tf!([( "Int", "Int" ) -> "Int"],
             ( Int(a), Int(b) ) => { Int( a.clone() - b ) }),
        "times" =>
        tf!([( "Int", "Int" ) -> "Int"],
             ( Int(a), Int(b) ) => { Int( a.clone() * b ) }),
        "zero?" =>
        tyf!( {"Type" "fn" : "param" => [ {"Type" "Int" :}], "ret" => (vr "Bool") },
              ( Int(a) ) => { val!(b   a == BigInt::from(0))}),
        "equal?" =>
        tyf!( {"Type" "fn" : "param" => [ {"Type" "Int" :}, {"Type" "Int" :} ],
                             "ret" => (vr "Bool")},
              ( Int(a), Int(b) ) => { val!(b a == b)} ),
        "zero" => tf!( "Int", val!(i 0) ),
        "one" => tf!( "Int", val!(i 1) ),
        "two" => tf!( "Int", val!(i 2) ),
        "three" => tf!( "Int", val!(i 3) ),
        "four" => tf!( "Int", val!(i 4) ),
        "five" => tf!( "Int", val!(i 5) ),
        "six" => tf!( "Int", val!(i 6) ),
        "seven" => tf!( "Int", val!(i 7) ),
        "eight" => tf!( "Int", val!(i 8) ),
        "nine" => tf!( "Int", val!(i 9) ),
        "ten" => tf!( "Int", val!(i 10) ),
        "false" => TypedValue { ty: ast!((vr "Bool")), val: val!(b false)},
        "true" => TypedValue { ty: ast!((vr "Bool")), val: val!(b true)}
    )
}

pub fn core_values() -> Assoc<Name, Value> { core_typed_values().map(&erase_type) }

// Helper for building an environment by reifying a bunch of Rust types
macro_rules! reified_ty_env {
    ( $($t:ty),* ) => {
        Assoc::new() $( .set(<$t>::ty_name(), Ty(<$t>::ty())))*
    };
}

pub fn core_types() -> Assoc<Name, Ty> {
    use core_type_forms::get__primitive_type;
    use runtime::reify::Reifiable;
    core_typed_values()
        .map(&erase_value)
        .set(
            n("Bool"),
            ty!({"Type" "enum" : "name" => [@"c" "True", "False"], "component" => [@"c" [], []]}),
        )
        // These need to be in the environment, not just atomic types
        //  because we sometimes look them up internally in the compiler
        //   in the environment,
        //  not just as programmers, looking them up by syntax, where this whole thing is a wash.
        .set(n("Pat"), get__primitive_type(n("Pat")))
        .set(n("Type"), get__primitive_type(n("Type")))
        .set(n("Expr"), get__primitive_type(n("Expr")))
        .set(n("Sequence"), get__primitive_type(n("Sequence")))
    // This is no good, we gotta reify generic types:
    // .set_assoc(&reified_ty_env!(
    // Name, ::grammar::FormPat, u8, ::beta::Beta, ::form::Form,
    // ::ast_walk::WalkRule<::ty::SynthTy>, ::ast_walk::WalkRule<::runtime::eval::QQuote>,
    // ::ast_walk::WalkRule<::runtime::eval::QQuoteDestr>))
}

pub fn get_core_envs() -> ::earley::CodeEnvs {
    (
        ::ast_walk::LazyWalkReses::new_wrapper(core_types()),
        ::ast_walk::LazyWalkReses::new_wrapper(core_values()),
    )
}

#[test]
fn basic_core_value_evaluation() {
    use core_forms::find_core_form;

    let cte = core_typed_values();
    let ce = cte.map(&erase_type);

    assert_eq!(
        eval(
            &ast!({ find_core_form( "Expr", "apply") ;
                "rator" => (vr "plus"),
                "rand" => [ (vr "one"), (vr "one") ]
            }),
            ce
        ),
        Ok(Int(BigInt::from(2)))
    );
}

#[test]
fn fixpoint_evaluation() {
    assert_eq!(
        eval(
            &ast!( {"Expr" "apply" : "rator" =>
        { "Expr" "apply" :
            "rator" => (vr "fix"),
            "rand" => [{ "Expr" "lambda" :
                 "param" => [@"p" "again" ],
                 "p_t" => [@"p" /* TODO */ (vr "TODO")  ],
                 "body" => (import [* ["param" : "p_t"]]
                 { "Expr" "lambda" :
                     "param" => [@"p" "n" ],
                     "p_t" => [@"p" { "Type" "Int" : } ],
                     "body" => (import [* ["param" : "p_t"]]
                     { "Expr" "match" :
                         "scrutinee" => { "Expr" "apply" : "rator" => (vr "zero?"),
                                                           "rand" => [(vr "n")] },
                         "p" => [@"c" {"Pat" "enum_pat" : "component" => [], "name" => "True"  },
                                      {"Pat" "enum_pat" : "component" => [], "name" => "False" }],
                         "arm" =>
                             [@"c"
                              (import ["p" = "scrutinee"] (vr "one")),
                              (import ["p" = "scrutinee"] { "Expr" "apply" :
                                   "rator" => (vr "times"),
                                   "rand" =>
                                       [(vr "n"),
                                        { "Expr" "apply" :
                                            "rator" => { "Expr" "apply" :
                                                "rator" => (vr "again"), "rand" => []},
                                            "rand" => [{ "Expr" "apply" :
                                                 "rator" => (vr "minus"),
                                                 "rand" => [(vr "n"), (vr "one")]}]}]})]})})}]},
        "rand" => [(vr "five")]}),
            core_values()
        ),
        Ok(val!(i 120))
    );
}
<commit_msg>Set up a core environment containing enough types for `SynEnv` to work.<commit_after>use ast::Ast;
use name::*;
use runtime::eval::{
    eval,
    Value::{self, *},
    BIF,
};
use std::rc::Rc;
use ty::Ty;
use util::assoc::Assoc;

use num::BigInt;

#[derive(Debug, Clone, PartialEq)]
pub struct TypedValue {
    pub ty: Ast,
    pub val: Value,
}

pub fn erase_type(tv: &TypedValue) -> Value { tv.val.clone() }
pub fn erase_value(tv: &TypedValue) -> Ty { Ty::new(tv.ty.clone()) }

pub fn core_typed_values() -> Assoc<Name, TypedValue> {
    assoc_n!(
        "fix" =>
        tyf!( { "Type" "forall_type" :
            "param" => ["F"], // has to be a function, but we don't know its arity
            "body" => (import [* [forall "param"]] { "Type" "fn" :
                "param" => [ { "Type" "fn" :
                    "param" => [{"Type" "fn" : "param" => [], "ret" => (vr "F") }],
                    "ret" => (vr "F")} ],
                "ret" => (vr "F") })},
            // TODO: built-in functions, even though none of them make sense here, shouldn't crash
            ( Function(cl) ) => {
                let new_env = cl.env.set(cl.params[0],
                    // reconstruct the invocation that caused this:
                    Function(Rc::new(::runtime::eval::Closure {
                        body: ast!({"Expr" "apply" :
                            "rator" => (vr "fix"),
                            "rand" => [(vr "orig_arg")]}),
                        params: vec![],
                        env: assoc_n!("orig_arg" => Function(cl.clone()),
                                      // TODO: `core_values` does the `map` every time...
                                      "fix" => core_values().find_or_panic(&n("fix")).clone())})));
                eval(&cl.body, new_env).unwrap() // TODO: should be able to return `Result`
            }
        ),
        "plus" =>
        tf!([( "Int", "Int" ) -> "Int"],
             ( Int(a), Int(b) ) => { Int( a.clone() + b ) }),
        "minus" =>
        tf!([( "Int", "Int" ) -> "Int"],
             ( Int(a), Int(b) ) => { Int( a.clone() - b ) }),
        "times" =>
        tf!([( "Int", "Int" ) -> "Int"],
             ( Int(a), Int(b) ) => { Int( a.clone() * b ) }),
        "zero?" =>
        tyf!( {"Type" "fn" : "param" => [ {"Type" "Int" :}], "ret" => (vr "Bool") },
              ( Int(a) ) => { val!(b   a == BigInt::from(0))}),
        "equal?" =>
        tyf!( {"Type" "fn" : "param" => [ {"Type" "Int" :}, {"Type" "Int" :} ],
                             "ret" => (vr "Bool")},
              ( Int(a), Int(b) ) => { val!(b a == b)} ),
        "zero" => tf!( "Int", val!(i 0) ),
        "one" => tf!( "Int", val!(i 1) ),
        "two" => tf!( "Int", val!(i 2) ),
        "three" => tf!( "Int", val!(i 3) ),
        "four" => tf!( "Int", val!(i 4) ),
        "five" => tf!( "Int", val!(i 5) ),
        "six" => tf!( "Int", val!(i 6) ),
        "seven" => tf!( "Int", val!(i 7) ),
        "eight" => tf!( "Int", val!(i 8) ),
        "nine" => tf!( "Int", val!(i 9) ),
        "ten" => tf!( "Int", val!(i 10) ),
        "false" => TypedValue { ty: ast!((vr "Bool")), val: val!(b false)},
        "true" => TypedValue { ty: ast!((vr "Bool")), val: val!(b true)}
    )
}

pub fn core_values() -> Assoc<Name, Value> { core_typed_values().map(&erase_type) }

// Helper for building an environment by reifying a bunch of Rust types
macro_rules! reified_ty_env {
    ( $($t:ty),* ) => {
        Assoc::new() $( .set(<$t>::ty_name(), Ty(<$t>::ty())))*
    };
}

pub fn core_types() -> Assoc<Name, Ty> {
    use core_type_forms::get__primitive_type;
    use runtime::reify::{Irr, Reifiable};
    core_typed_values()
        .map(&erase_value)
        .set(
            n("Bool"),
            ty!({"Type" "enum" : "name" => [@"c" "True", "False"], "component" => [@"c" [], []]}),
        )
        // These need to be in the environment, not just atomic types
        //  because we sometimes look them up internally in the compiler
        //   in the environment,
        //  not just as programmers, looking them up by syntax, where this whole thing is a wash.
        .set(n("Pat"), get__primitive_type(n("Pat")))
        .set(n("Type"), get__primitive_type(n("Type")))
        .set(n("Expr"), get__primitive_type(n("Expr")))
        .set(n("Sequence"), get__primitive_type(n("Sequence")))
        .set_assoc(&reified_ty_env!(
            Option<Irr>, u8, usize,
            ::util::assoc::AssocNode<Irr, Irr>, ::util::assoc::Assoc<Irr, Irr>,
            ::util::mbe::EnvMBE<Irr>,
            Name, ::ast::Ast, ::beta::Beta, ::beta::ExportBeta,
            ::grammar::FormPat, ::grammar::SyntaxExtension, ::grammar::Scanner,
            ::form::Form, ::form::EitherPN<Irr, Irr>, ::ast_walk::WalkRule<Irr>,
            ::runtime::eval::QQuote, ::runtime::eval::QQuoteDestr,
            ::runtime::eval::Eval, ::runtime::eval::Destructure,
            ::ty::SynthTy, ::ty::UnpackTy,
            ::ty_compare::Canonicalize, ::ty_compare::Subtype
            ))
}

pub fn get_core_envs() -> ::earley::CodeEnvs {
    (
        ::ast_walk::LazyWalkReses::new_wrapper(core_types()),
        ::ast_walk::LazyWalkReses::new_wrapper(core_values()),
    )
}

#[test]
fn basic_core_value_evaluation() {
    use core_forms::find_core_form;

    let cte = core_typed_values();
    let ce = cte.map(&erase_type);

    assert_eq!(
        eval(
            &ast!({ find_core_form( "Expr", "apply") ;
                "rator" => (vr "plus"),
                "rand" => [ (vr "one"), (vr "one") ]
            }),
            ce
        ),
        Ok(Int(BigInt::from(2)))
    );
}

#[test]
fn fixpoint_evaluation() {
    assert_eq!(
        eval(
            &ast!( {"Expr" "apply" : "rator" =>
        { "Expr" "apply" :
            "rator" => (vr "fix"),
            "rand" => [{ "Expr" "lambda" :
                 "param" => [@"p" "again" ],
                 "p_t" => [@"p" /* TODO */ (vr "TODO")  ],
                 "body" => (import [* ["param" : "p_t"]]
                 { "Expr" "lambda" :
                     "param" => [@"p" "n" ],
                     "p_t" => [@"p" { "Type" "Int" : } ],
                     "body" => (import [* ["param" : "p_t"]]
                     { "Expr" "match" :
                         "scrutinee" => { "Expr" "apply" : "rator" => (vr "zero?"),
                                                           "rand" => [(vr "n")] },
                         "p" => [@"c" {"Pat" "enum_pat" : "component" => [], "name" => "True"  },
                                      {"Pat" "enum_pat" : "component" => [], "name" => "False" }],
                         "arm" =>
                             [@"c"
                              (import ["p" = "scrutinee"] (vr "one")),
                              (import ["p" = "scrutinee"] { "Expr" "apply" :
                                   "rator" => (vr "times"),
                                   "rand" =>
                                       [(vr "n"),
                                        { "Expr" "apply" :
                                            "rator" => { "Expr" "apply" :
                                                "rator" => (vr "again"), "rand" => []},
                                            "rand" => [{ "Expr" "apply" :
                                                 "rator" => (vr "minus"),
                                                 "rand" => [(vr "n"), (vr "one")]}]}]})]})})}]},
        "rand" => [(vr "five")]}),
            core_values()
        ),
        Ok(val!(i 120))
    );
}
<|endoftext|>


<commit_before>extern crate cairo;

extern crate gtk;
use gtk::{Inhibit, ToolButtonExt, WidgetExt};

#[cfg(feature = "profiling-audio-draw")]
use chrono::Utc;

use std::boxed::Box;

use std::rc::Rc;
use std::cell::RefCell;

use std::sync::{Arc, Mutex};

use media::{Context, DoubleAudioBuffer, SampleExtractor, Timestamp};

use super::{ControllerState, DoubleWaveformBuffer, MainController, WaveformBuffer,
            WaveformConditions, BACKGROUND_COLOR};

const BUFFER_DURATION: u64 = 60_000_000_000; // 60 s
const MIN_REQ_DURATION: f64 = 1_953_125f64; // 2 ms / 1000 px
const MAX_REQ_DURATION: f64 = 32_000_000_000f64; // 32 s / 1000 px
const INIT_REQ_DURATION: f64 = 4_000_000_000f64; // 4 s / 1000 px
const STEP_REQ_DURATION: f64 = 2f64;

pub struct AudioController {
    container: gtk::Box,
    drawingarea: gtk::DrawingArea,
    zoom_in_btn: gtk::ToolButton,
    zoom_out_btn: gtk::ToolButton,

    is_active: bool,
    playback_needs_refresh: bool,

    requested_duration: f64,

    waveform_mtx: Arc<Mutex<Box<SampleExtractor>>>,
    dbl_buffer_mtx: Arc<Mutex<DoubleAudioBuffer>>,
}

impl AudioController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<Self>> {
        let dbl_buffer_mtx = DoubleWaveformBuffer::new_mutex(BUFFER_DURATION);
        let waveform_mtx = dbl_buffer_mtx
            .lock()
            .expect("AudioController::new: couldn't lock dbl_buffer_mtx")
            .get_exposed_buffer_mtx();

        Rc::new(RefCell::new(AudioController {
            container: builder.get_object("audio-container").unwrap(),
            drawingarea: builder.get_object("audio-drawingarea").unwrap(),
            zoom_in_btn: builder.get_object("audio_zoom_in-toolbutton").unwrap(),
            zoom_out_btn: builder.get_object("audio_zoom_out-toolbutton").unwrap(),

            is_active: false,
            playback_needs_refresh: false,

            requested_duration: INIT_REQ_DURATION,
            waveform_mtx: waveform_mtx,
            dbl_buffer_mtx: dbl_buffer_mtx,
        }))
    }

    pub fn register_callbacks(
        this_rc: &Rc<RefCell<Self>>,
        main_ctrl: &Rc<RefCell<MainController>>,
    ) {
        let this = this_rc.borrow();

        // draw
        let this_clone = Rc::clone(this_rc);
        this.drawingarea
            .connect_draw(move |drawing_area, cairo_ctx| {
                let mut this = this_clone.borrow_mut();
                this.draw(drawing_area, cairo_ctx)
            });

        // widget size changed
        let main_ctrl_clone = Rc::clone(main_ctrl);
        let this_clone = Rc::clone(this_rc);
        this.drawingarea.connect_size_allocate(move |_, _| {
            this_clone.borrow_mut().refresh(&main_ctrl_clone);
        });

        // click in drawing_area
        let main_ctrl_clone = Rc::clone(main_ctrl);
        let this_clone = Rc::clone(this_rc);
        this.drawingarea
            .connect_button_press_event(move |_, event_button| {
                let button = event_button.get_button();
                if button == 1 {
                    if let Some(position) = {
                        let mut this = this_clone.borrow_mut();
                        let waveform_buffer_grd = &mut *this.waveform_mtx
                            .lock()
                            .expect("Couldn't lock waveform buffer in audio controller draw");
                        waveform_buffer_grd
                            .as_mut_any()
                            .downcast_mut::<WaveformBuffer>()
                            .expect(concat!(
                                "AudioController::drawingarea::button_press_event ",
                                "SamplesExtratctor is not a waveform buffer",
                            ))
                            .get_position(event_button.get_position().0)
                    } {
                        main_ctrl_clone.borrow_mut().seek(position, true); // accurate (slow)
                    }
                }
                Inhibit(true)
            });

        // click zoom in
        let main_ctrl_clone = Rc::clone(main_ctrl);
        let this_clone = Rc::clone(this_rc);
        this.zoom_in_btn.connect_clicked(move |_| {
            let mut this = this_clone.borrow_mut();
            this.requested_duration /= STEP_REQ_DURATION;
            if this.requested_duration >= MIN_REQ_DURATION {
                this.refresh(&main_ctrl_clone);
            } else {
                this.requested_duration = MIN_REQ_DURATION;
            }
        });

        // click zoom out
        let main_ctrl_clone = Rc::clone(main_ctrl);
        let this_clone = Rc::clone(this_rc);
        this.zoom_out_btn.connect_clicked(move |_| {
            let mut this = this_clone.borrow_mut();
            this.requested_duration *= STEP_REQ_DURATION;
            if this.requested_duration <= MAX_REQ_DURATION {
                this.refresh(&main_ctrl_clone);
            } else {
                this.requested_duration = MAX_REQ_DURATION;
            }
        });
    }

    pub fn cleanup(&mut self) {
        self.is_active = false;
        self.playback_needs_refresh = false;
        {
            self.dbl_buffer_mtx
                .lock()
                .expect("AudioController::cleanup: Couldn't lock dbl_buffer_mtx")
                .cleanup();
        }
        self.requested_duration = INIT_REQ_DURATION;
        self.drawingarea.queue_draw();
    }

    pub fn get_dbl_buffer_mtx(&self) -> Arc<Mutex<DoubleAudioBuffer>> {
        Arc::clone(&self.dbl_buffer_mtx)
    }

    pub fn new_media(&mut self, context: &Context) {
        let has_audio = context
            .info
            .lock()
            .expect("Failed to lock media info while initializing audio controller")
            .audio_best
            .is_some();

        if has_audio {
            self.is_active = true;
            self.container.show();
        } else {
            self.container.hide();
        }
    }

    pub fn seek(&mut self, position: u64, state: &ControllerState) {
        {
            self.waveform_mtx
                .lock()
                .expect("AudioController::seek: Couldn't lock waveform_mtx")
                .as_mut_any()
                .downcast_mut::<WaveformBuffer>()
                .expect("AudioController::seek: SamplesExtratctor is not a WaveformBuffer")
                .seek(position, *state == ControllerState::Playing);
        }

        if *state == ControllerState::Paused {
            // refresh the buffer in order to render the waveform
            // with samples that might not be rendered in current WaveformImage yet
            self.dbl_buffer_mtx
                .lock()
                .expect("AudioController::seek: couldn't lock dbl_buffer_mtx")
                .refresh(false); // don't keep continuity
        }
        self.drawingarea.queue_draw();
    }

    pub fn tick(&mut self) {
        if self.is_active {
            if self.playback_needs_refresh {
                #[cfg(feature = "trace-audio-controller")]
                println!("AudioController::tick forcing refresh");

                self.dbl_buffer_mtx
                    .lock()
                    .expect("AudioController::tick: couldn't lock dbl_buffer_mtx")
                    .refresh(true); // keep continuity
            }

            self.drawingarea.queue_draw();
        }
    }

    fn clean_cairo_context(&self, cr: &cairo::Context) {
        cr.set_source_rgb(BACKGROUND_COLOR.0, BACKGROUND_COLOR.1, BACKGROUND_COLOR.2);
        cr.paint();
    }

    fn draw(&mut self, drawingarea: &gtk::DrawingArea, cr: &cairo::Context) -> Inhibit {
        #[cfg(feature = "profiling-audio-draw")]
        let before_init = Utc::now();

        let allocation = drawingarea.get_allocation();
        if allocation.width.is_negative() {
            #[cfg(feature = "trace-audio-controller")]
            println!(
                "AudioController::draw negative allocation.width: {}",
                allocation.width
            );

            self.clean_cairo_context(cr);
            return Inhibit(true);
        }

        #[cfg(feature = "profiling-audio-draw")]
        let before_lock = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_cndt = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_image = Utc::now();

        let image_positions = {
            let waveform_grd = &mut *self.waveform_mtx
                .lock()
                .expect("AudioController::draw: couldn't lock waveform_mtx");
            let waveform_buffer = waveform_grd
                .as_mut_any()
                .downcast_mut::<WaveformBuffer>()
                .expect("AudioController::draw: SamplesExtratctor is not a WaveformBuffer");

            #[cfg(feature = "profiling-audio-draw")]
            let _before_cndt = Utc::now();

            waveform_buffer.update_conditions(
                self.requested_duration,
                allocation.width,
                allocation.height,
            );

            self.playback_needs_refresh = waveform_buffer.playback_needs_refresh;

            match waveform_buffer.get_image() {
                Some((image, image_positions)) => {
                    #[cfg(feature = "profiling-audio-draw")]
                    let _before_image = Utc::now();

                    cr.set_source_surface(image, -image_positions.first.x, 0f64);
                    cr.paint();

                    image_positions
                }
                None => {
                    self.clean_cairo_context(cr);

                    #[cfg(feature = "trace-audio-controller")]
                    println!("AudioController::draw no image");

                    return Inhibit(true);
                }
            }
        };

        #[cfg(feature = "profiling-audio-draw")]
        let before_pos = Utc::now();

        let height = f64::from(allocation.height);
        let width = f64::from(allocation.width);
        cr.scale(1f64, 1f64);
        cr.set_source_rgb(1f64, 1f64, 0f64);
        cr.set_font_size(14f64);

        // first position
        let first_text = Timestamp::format(image_positions.first.timestamp, false);
        let first_text_width = 2f64 + cr.text_extents(&first_text).width;
        cr.move_to(2f64, 30f64);
        cr.show_text(&first_text);

        // last position
        if let Some(last_pos) = image_positions.last {
            let last_text = Timestamp::format(last_pos.timestamp, false);
            let last_text_width = cr.text_extents(&last_text).width;
            if last_pos.x + last_text_width > first_text_width + 10f64 {
                // last text won't overlap with first text
                // align actual width to a 15px multiple box in order to avoid
                // variations due to actual size of individual digits
                let last_text_width = (last_text_width / 15f64).ceil() * 15f64;
                cr.move_to(last_pos.x - last_text_width, 30f64);
                cr.show_text(&last_text);
            }
        }

        if let Some(current_pos) = image_positions.current {
            // draw current pos
            let cursor_text = Timestamp::format(current_pos.timestamp, true);
            let cursor_text_width = 5f64 + cr.text_extents(&cursor_text).width;
            let cursor_text_x = if current_pos.x + cursor_text_width < width {
                current_pos.x + 5f64
            } else {
                current_pos.x - cursor_text_width
            };
            cr.move_to(cursor_text_x, 15f64);
            cr.show_text(&cursor_text);

            cr.set_line_width(1f64);
            cr.move_to(current_pos.x, 0f64);
            cr.line_to(current_pos.x, height);
            cr.stroke();
        }

        #[cfg(feature = "profiling-audio-draw")]
        let end = Utc::now();

        #[cfg(feature = "profiling-audio-draw")]
        println!(
            "audio-draw,{},{},{},{},{},{}",
            before_init.time().format("%H:%M:%S%.6f"),
            before_lock.time().format("%H:%M:%S%.6f"),
            _before_cndt.time().format("%H:%M:%S%.6f"),
            _before_image.time().format("%H:%M:%S%.6f"),
            before_pos.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );

        Inhibit(true)
    }

    fn refresh(&mut self, main_ctrl: &Rc<RefCell<MainController>>) {
        let allocation = self.drawingarea.get_allocation();
        {
            // refresh the buffer in order to render the waveform
            // in latest conditions
            let requested_duration = self.requested_duration;
            self.dbl_buffer_mtx
                .lock()
                .expect("AudioController::size-allocate: couldn't lock dbl_buffer_mtx")
                .refresh_with_conditions(
                    Box::new(WaveformConditions::new(
                        requested_duration,
                        allocation.width,
                        allocation.height,
                    )),
                    *main_ctrl.borrow().get_state() == ControllerState::Playing,
                );
        }

        self.drawingarea.queue_draw();
    }
}
<commit_msg>Fix: end position text overlaps start position text (#66)<commit_after>extern crate cairo;

extern crate gtk;
use gtk::{Inhibit, ToolButtonExt, WidgetExt};

#[cfg(feature = "profiling-audio-draw")]
use chrono::Utc;

use std::boxed::Box;

use std::rc::Rc;
use std::cell::RefCell;

use std::sync::{Arc, Mutex};

use media::{Context, DoubleAudioBuffer, SampleExtractor, Timestamp};

use super::{ControllerState, DoubleWaveformBuffer, MainController, WaveformBuffer,
            WaveformConditions, BACKGROUND_COLOR};

const BUFFER_DURATION: u64 = 60_000_000_000; // 60 s
const MIN_REQ_DURATION: f64 = 1_953_125f64; // 2 ms / 1000 px
const MAX_REQ_DURATION: f64 = 32_000_000_000f64; // 32 s / 1000 px
const INIT_REQ_DURATION: f64 = 4_000_000_000f64; // 4 s / 1000 px
const STEP_REQ_DURATION: f64 = 2f64;

pub struct AudioController {
    container: gtk::Box,
    drawingarea: gtk::DrawingArea,
    zoom_in_btn: gtk::ToolButton,
    zoom_out_btn: gtk::ToolButton,

    is_active: bool,
    playback_needs_refresh: bool,

    requested_duration: f64,

    waveform_mtx: Arc<Mutex<Box<SampleExtractor>>>,
    dbl_buffer_mtx: Arc<Mutex<DoubleAudioBuffer>>,
}

impl AudioController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<Self>> {
        let dbl_buffer_mtx = DoubleWaveformBuffer::new_mutex(BUFFER_DURATION);
        let waveform_mtx = dbl_buffer_mtx
            .lock()
            .expect("AudioController::new: couldn't lock dbl_buffer_mtx")
            .get_exposed_buffer_mtx();

        Rc::new(RefCell::new(AudioController {
            container: builder.get_object("audio-container").unwrap(),
            drawingarea: builder.get_object("audio-drawingarea").unwrap(),
            zoom_in_btn: builder.get_object("audio_zoom_in-toolbutton").unwrap(),
            zoom_out_btn: builder.get_object("audio_zoom_out-toolbutton").unwrap(),

            is_active: false,
            playback_needs_refresh: false,

            requested_duration: INIT_REQ_DURATION,
            waveform_mtx: waveform_mtx,
            dbl_buffer_mtx: dbl_buffer_mtx,
        }))
    }

    pub fn register_callbacks(
        this_rc: &Rc<RefCell<Self>>,
        main_ctrl: &Rc<RefCell<MainController>>,
    ) {
        let this = this_rc.borrow();

        // draw
        let this_clone = Rc::clone(this_rc);
        this.drawingarea
            .connect_draw(move |drawing_area, cairo_ctx| {
                let mut this = this_clone.borrow_mut();
                this.draw(drawing_area, cairo_ctx)
            });

        // widget size changed
        let main_ctrl_clone = Rc::clone(main_ctrl);
        let this_clone = Rc::clone(this_rc);
        this.drawingarea.connect_size_allocate(move |_, _| {
            this_clone.borrow_mut().refresh(&main_ctrl_clone);
        });

        // click in drawing_area
        let main_ctrl_clone = Rc::clone(main_ctrl);
        let this_clone = Rc::clone(this_rc);
        this.drawingarea
            .connect_button_press_event(move |_, event_button| {
                let button = event_button.get_button();
                if button == 1 {
                    if let Some(position) = {
                        let mut this = this_clone.borrow_mut();
                        let waveform_buffer_grd = &mut *this.waveform_mtx
                            .lock()
                            .expect("Couldn't lock waveform buffer in audio controller draw");
                        waveform_buffer_grd
                            .as_mut_any()
                            .downcast_mut::<WaveformBuffer>()
                            .expect(concat!(
                                "AudioController::drawingarea::button_press_event ",
                                "SamplesExtratctor is not a waveform buffer",
                            ))
                            .get_position(event_button.get_position().0)
                    } {
                        main_ctrl_clone.borrow_mut().seek(position, true); // accurate (slow)
                    }
                }
                Inhibit(true)
            });

        // click zoom in
        let main_ctrl_clone = Rc::clone(main_ctrl);
        let this_clone = Rc::clone(this_rc);
        this.zoom_in_btn.connect_clicked(move |_| {
            let mut this = this_clone.borrow_mut();
            this.requested_duration /= STEP_REQ_DURATION;
            if this.requested_duration >= MIN_REQ_DURATION {
                this.refresh(&main_ctrl_clone);
            } else {
                this.requested_duration = MIN_REQ_DURATION;
            }
        });

        // click zoom out
        let main_ctrl_clone = Rc::clone(main_ctrl);
        let this_clone = Rc::clone(this_rc);
        this.zoom_out_btn.connect_clicked(move |_| {
            let mut this = this_clone.borrow_mut();
            this.requested_duration *= STEP_REQ_DURATION;
            if this.requested_duration <= MAX_REQ_DURATION {
                this.refresh(&main_ctrl_clone);
            } else {
                this.requested_duration = MAX_REQ_DURATION;
            }
        });
    }

    pub fn cleanup(&mut self) {
        self.is_active = false;
        self.playback_needs_refresh = false;
        {
            self.dbl_buffer_mtx
                .lock()
                .expect("AudioController::cleanup: Couldn't lock dbl_buffer_mtx")
                .cleanup();
        }
        self.requested_duration = INIT_REQ_DURATION;
        self.drawingarea.queue_draw();
    }

    pub fn get_dbl_buffer_mtx(&self) -> Arc<Mutex<DoubleAudioBuffer>> {
        Arc::clone(&self.dbl_buffer_mtx)
    }

    pub fn new_media(&mut self, context: &Context) {
        let has_audio = context
            .info
            .lock()
            .expect("Failed to lock media info while initializing audio controller")
            .audio_best
            .is_some();

        if has_audio {
            self.is_active = true;
            self.container.show();
        } else {
            self.container.hide();
        }
    }

    pub fn seek(&mut self, position: u64, state: &ControllerState) {
        {
            self.waveform_mtx
                .lock()
                .expect("AudioController::seek: Couldn't lock waveform_mtx")
                .as_mut_any()
                .downcast_mut::<WaveformBuffer>()
                .expect("AudioController::seek: SamplesExtratctor is not a WaveformBuffer")
                .seek(position, *state == ControllerState::Playing);
        }

        if *state == ControllerState::Paused {
            // refresh the buffer in order to render the waveform
            // with samples that might not be rendered in current WaveformImage yet
            self.dbl_buffer_mtx
                .lock()
                .expect("AudioController::seek: couldn't lock dbl_buffer_mtx")
                .refresh(false); // don't keep continuity
        }
        self.drawingarea.queue_draw();
    }

    pub fn tick(&mut self) {
        if self.is_active {
            if self.playback_needs_refresh {
                #[cfg(feature = "trace-audio-controller")]
                println!("AudioController::tick forcing refresh");

                self.dbl_buffer_mtx
                    .lock()
                    .expect("AudioController::tick: couldn't lock dbl_buffer_mtx")
                    .refresh(true); // keep continuity
            }

            self.drawingarea.queue_draw();
        }
    }

    fn clean_cairo_context(&self, cr: &cairo::Context) {
        cr.set_source_rgb(BACKGROUND_COLOR.0, BACKGROUND_COLOR.1, BACKGROUND_COLOR.2);
        cr.paint();
    }

    fn draw(&mut self, drawingarea: &gtk::DrawingArea, cr: &cairo::Context) -> Inhibit {
        #[cfg(feature = "profiling-audio-draw")]
        let before_init = Utc::now();

        let allocation = drawingarea.get_allocation();
        if allocation.width.is_negative() {
            #[cfg(feature = "trace-audio-controller")]
            println!(
                "AudioController::draw negative allocation.width: {}",
                allocation.width
            );

            self.clean_cairo_context(cr);
            return Inhibit(true);
        }

        #[cfg(feature = "profiling-audio-draw")]
        let before_lock = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_cndt = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_image = Utc::now();

        let image_positions = {
            let waveform_grd = &mut *self.waveform_mtx
                .lock()
                .expect("AudioController::draw: couldn't lock waveform_mtx");
            let waveform_buffer = waveform_grd
                .as_mut_any()
                .downcast_mut::<WaveformBuffer>()
                .expect("AudioController::draw: SamplesExtratctor is not a WaveformBuffer");

            #[cfg(feature = "profiling-audio-draw")]
            let _before_cndt = Utc::now();

            waveform_buffer.update_conditions(
                self.requested_duration,
                allocation.width,
                allocation.height,
            );

            self.playback_needs_refresh = waveform_buffer.playback_needs_refresh;

            match waveform_buffer.get_image() {
                Some((image, image_positions)) => {
                    #[cfg(feature = "profiling-audio-draw")]
                    let _before_image = Utc::now();

                    cr.set_source_surface(image, -image_positions.first.x, 0f64);
                    cr.paint();

                    image_positions
                }
                None => {
                    self.clean_cairo_context(cr);

                    #[cfg(feature = "trace-audio-controller")]
                    println!("AudioController::draw no image");

                    return Inhibit(true);
                }
            }
        };

        #[cfg(feature = "profiling-audio-draw")]
        let before_pos = Utc::now();

        let height = f64::from(allocation.height);
        let width = f64::from(allocation.width);
        cr.scale(1f64, 1f64);
        cr.set_source_rgb(1f64, 1f64, 0f64);
        cr.set_font_size(14f64);

        // first position
        let first_text = Timestamp::format(image_positions.first.timestamp, false);
        let first_text_width = 2f64 + cr.text_extents(&first_text).width;
        cr.move_to(2f64, 30f64);
        cr.show_text(&first_text);

        // last position
        if let Some(last_pos) = image_positions.last {
            let last_text = Timestamp::format(last_pos.timestamp, false);
            // align actual width to a 15px multiple box in order to avoid
            // variations due to actual size of individual digits
            let last_text_width = (cr.text_extents(&last_text).width / 15f64).ceil() * 15f64;
            if last_pos.x - last_text_width > first_text_width + 5f64 {
                // last text won't overlap with first text
                cr.move_to(last_pos.x - last_text_width, 30f64);
                cr.show_text(&last_text);
            }
        }

        if let Some(current_pos) = image_positions.current {
            // draw current pos
            let cursor_text = Timestamp::format(current_pos.timestamp, true);
            let cursor_text_width = 5f64 + cr.text_extents(&cursor_text).width;
            let cursor_text_x = if current_pos.x + cursor_text_width < width {
                current_pos.x + 5f64
            } else {
                current_pos.x - cursor_text_width
            };
            cr.move_to(cursor_text_x, 15f64);
            cr.show_text(&cursor_text);

            cr.set_line_width(1f64);
            cr.move_to(current_pos.x, 0f64);
            cr.line_to(current_pos.x, height);
            cr.stroke();
        }

        #[cfg(feature = "profiling-audio-draw")]
        let end = Utc::now();

        #[cfg(feature = "profiling-audio-draw")]
        println!(
            "audio-draw,{},{},{},{},{},{}",
            before_init.time().format("%H:%M:%S%.6f"),
            before_lock.time().format("%H:%M:%S%.6f"),
            _before_cndt.time().format("%H:%M:%S%.6f"),
            _before_image.time().format("%H:%M:%S%.6f"),
            before_pos.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );

        Inhibit(true)
    }

    fn refresh(&mut self, main_ctrl: &Rc<RefCell<MainController>>) {
        let allocation = self.drawingarea.get_allocation();
        {
            // refresh the buffer in order to render the waveform
            // in latest conditions
            let requested_duration = self.requested_duration;
            self.dbl_buffer_mtx
                .lock()
                .expect("AudioController::size-allocate: couldn't lock dbl_buffer_mtx")
                .refresh_with_conditions(
                    Box::new(WaveformConditions::new(
                        requested_duration,
                        allocation.width,
                        allocation.height,
                    )),
                    *main_ctrl.borrow().get_state() == ControllerState::Playing,
                );
        }

        self.drawingarea.queue_draw();
    }
}
<|endoftext|>


<commit_before>use ::xen::arch::mem::*;
use ::xen::event_channels::send;

pub trait ReadableRing {
    fn read(&mut self) {
        // TODO
    }
    fn input_buffer(&mut self) -> &mut [u8];
    fn get_in_cons(&self) -> usize;
    fn get_in_prod(&self) -> usize;
    fn set_in_cons(&mut self, usize);
    fn set_in_prod(&mut self, usize);
    fn input_mask(&mut self) -> usize {
        let len = self.input_buffer().len();
        return len - 1;
    }
}
pub trait WritableRing {
    fn output_buffer(&mut self) -> &mut [u8];

    fn get_out_cons(&self) -> usize;
    fn get_out_prod(&self) -> usize;
    fn set_out_cons(&mut self, usize);
    fn set_out_prod(&mut self, usize);

    fn output_mask(&mut self) -> usize {
        let len = self.output_buffer().len();
        return len - 1;
    }

    fn write(&mut self, buf : &[u8]) {
        let mut sent = 0usize;

        let cons = self.get_out_cons();
        let mut prod = self.get_out_prod();

        mb();

        {
            let output_mask = { self.output_mask() };
            let output_len = { self.output_buffer().len() };
            let mut output = self.output_buffer();

            while (sent < buf.len()) && ((prod - cons) < output_len) {
                let idx = prod & output_mask; // mask the index to make sure we don't overflow
                prod = prod + 1;
                output[idx] = buf[sent];
                sent = sent + 1;
            }
        }

        wmb();

        self.set_out_prod(prod);

    }

    fn write_notify(&mut self, buf : &[u8], evtchn : u32) {
        self.write(buf);
        unsafe { send(evtchn); }
    }

}
<commit_msg>Fixed stupid braces<commit_after>use ::xen::arch::mem::*;
use ::xen::event_channels::send;

pub trait ReadableRing {
    fn read(&mut self) {
        // TODO
    }
    fn input_buffer(&mut self) -> &mut [u8];
    fn get_in_cons(&self) -> usize;
    fn get_in_prod(&self) -> usize;
    fn set_in_cons(&mut self, usize);
    fn set_in_prod(&mut self, usize);
    fn input_mask(&mut self) -> usize {
        let len = self.input_buffer().len();
        return len - 1;
    }
}

pub trait WritableRing {
    fn output_buffer(&mut self) -> &mut [u8];

    fn get_out_cons(&self) -> usize;
    fn get_out_prod(&self) -> usize;
    fn set_out_cons(&mut self, usize);
    fn set_out_prod(&mut self, usize);

    fn output_mask(&mut self) -> usize {
        let len = self.output_buffer().len();
        return len - 1;
    }

    fn write(&mut self, buf : &[u8]) {
        let mut sent = 0usize;

        let cons = self.get_out_cons();
        let mut prod = self.get_out_prod();

        mb();

        {
            let output_mask = self.output_mask();
            let mut output = self.output_buffer();
            let output_len = output.len();

            while (sent < buf.len()) && ((prod - cons) < output_len) {
                let idx = prod & output_mask; // mask the index to make sure we don't overflow
                prod = prod + 1;
                output[idx] = buf[sent];
                sent = sent + 1;
            }
        }

        wmb();

        self.set_out_prod(prod);

    }

    fn write_notify(&mut self, buf : &[u8], evtchn : u32) {
        self.write(buf);
        unsafe { send(evtchn); }
    }
}
<|endoftext|>


<commit_before>extern crate xml;

use std::io::{Read, Write, Seek, SeekFrom};
use self::xml::reader::*;

/// This function takes a Reader `data_source`, consumes it,
/// indexes it and outputs the result in the `index_out` Writer.
pub fn generate_index<R: Read + Seek>(data_source: R, index_out: &mut Write) {
    let mut xml_reader = EventReader::new(data_source);
    // The XML file is made of lots of tags, but we only want to keep <page> events.
    while let Some(page_pos) = skip_until_page_start(&mut xml_reader) {
        if let Some(title) = extract_next_title(&mut xml_reader) {
            index_out.write(format!("{},{}\r\n", page_pos, title).as_bytes());
        }
    }
}
/// Iterate over all XmlEvents in the `reader` until a <page> is found.
/// If an error occurs while reading, or the end of the file is reached,
/// `None` is returned.
fn skip_until_page_start<R: Read + Seek>(reader: &mut EventReader<R>) -> Option<u64> {
    let page_tag = "<page>";
    // Read all events as long as we find some:
    while let Ok(event) = reader.next() {
        // If the event is a start element:
        if let XmlEvent::StartElement{name, ..} = event {
            // And it is for page:
            if name.local_name == page_tag[1..page_tag.len() - 1] /* Test if the name is the inside of the tag */ {
                // Stop iterating, we found the page:
                // Go back right before the "page" tag and return that seek position:
                return Some(reader.source_mut().seek(SeekFrom::Current(-1 * page_tag.len() as i64)).unwrap());
            }
        }
    }
    // No more pages. Return nothing.
    return None;
}

fn extract_next_title<R: Read>(reader: &mut EventReader<R>) -> Option<String> {
    // Read all events until we get a title:
    while let Ok(event) = reader.next() {
        // If the event is a start element:
        if let XmlEvent::StartElement{name, ..} = event {
            // And it is for page:
            if name.local_name == "title" {
                // Read an extra tag; it is the content of the title:
                if let Ok(XmlEvent::Characters(title)) = reader.next() {
                    // Extract the title and return it:
                    return Some(title);
                } else {
                    // Did not get a title; unexpected !! Return nothing for now.
                    // TODO: Return proper errors.
                    return None;
                }
            }
        }
    }
    // Reached end of file (or got an error) while looking for <title>, return nothing:
    return None;
}

#[cfg(test)]
mod tests;
<commit_msg>Only index actual articles (namespace 0) from the Wiki XML Data file.<commit_after>extern crate xml;

use std::io::{Read, Write, Seek, SeekFrom};
use self::xml::reader::*;

/// This function takes a Reader `data_source`, consumes it,
/// indexes it and outputs the result in the `index_out` Writer.
pub fn generate_index<R: Read + Seek>(data_source: R, index_out: &mut Write) {
    println!("Starting to index file...");
    let mut xml_reader = EventReader::new(data_source);
    // The XML file is made of lots of tags, but we only want to keep <page> events.
    while let Some(page_pos) = skip_until_page_start(&mut xml_reader) {
        if let Some(title) = extract_next_title(&mut xml_reader) {
            if let Some(ns) = extract_next_namespace(&mut xml_reader) {
                // Only consider namespace 0 (default articles)
                // Skip the others (files, templates, gadgets, etc..)
                if ns == "0" {
                    index_out.write(format!("{},{}\r\n", page_pos, title).as_bytes());
                }
            }
        }
    }
    println!("Done reading!");
}
/// Iterate over all XmlEvents in the `reader` until a <page> is found.
/// If an error occurs while reading, or the end of the file is reached,
/// `None` is returned.
fn skip_until_page_start<R: Read + Seek>(reader: &mut EventReader<R>) -> Option<u64> {
    let page_tag = "<page>";
    // Read all events as long as we find some:
    while let Ok(event) = reader.next() {
        // If the event is a start element:
        if let XmlEvent::StartElement{name, ..} = event {
            // And it is for page:
            if name.local_name == page_tag[1..page_tag.len() - 1] /* Test if the name is the inside of the tag */ {
                // Stop iterating, we found the page:
                // Go back right before the "page" tag and return that seek position:
                return Some(reader.source_mut().seek(SeekFrom::Current(-1 * page_tag.len() as i64)).unwrap());
            }
        }
    }
    // No more pages. Return nothing.
    return None;
}

fn extract_next_title<R: Read>(reader: &mut EventReader<R>) -> Option<String> {
    // Read all events until we get a title:
    while let Ok(event) = reader.next() {
        // If the event is a start element:
        if let XmlEvent::StartElement{name, ..} = event {
            // And it is for page:
            if name.local_name == "title" {
                // Read an extra tag; it is the content of the title:
                if let Ok(XmlEvent::Characters(title)) = reader.next() {
                    // Extract the title and return it:
                    return Some(title);
                } else {
                    // Did not get a title; unexpected !! Return nothing for now.
                    // TODO: Return proper errors.
                    return None;
                }
            }
        }
    }
    // Reached end of file (or got an error) while looking for <title>, return nothing:
    return None;
}

fn extract_next_namespace<R: Read>(reader: &mut EventReader<R>) -> Option<String> {
    // Read all events until we get a title:
    while let Ok(event) = reader.next() {
        // If the event is a start element:
        if let XmlEvent::StartElement{name, ..} = event {
            // And it is for page:
            if name.local_name == "ns" {
                // Read an extra tag; it is the content of the namespace tag:
                if let Ok(XmlEvent::Characters(ns)) = reader.next() {
                    // Extract the title and return it:
                    return Some(ns);
                } else {
                    // Did not get a title; unexpected !! Return nothing for now.
                    // TODO: Return proper errors.
                    return None;
                }
            }
        }
    }
    // Reached end of file (or got an error) while looking for <title>, return nothing:
    return None;
}

#[cfg(test)]
mod tests;
<|endoftext|>