Library ArchSemX86.OperationalX86TSO


From ASCommon Require Import Options.
From ASCommon Require Import Common Exec FMon.

From stdpp Require Import base options.

Require Import X86Inst.

This is an implementation of the x86-TSO operational concurrency model, as defined in https://www.cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.pdf

Section Model.
The number of hardware threads
  Context {threads : nat}.

Types

A memory entry in a write-buffer
  Record buffer_entry := {
      addr: address;
      size: N;
      val: bv (8 × size)
    }.

The TSO model internal state
  Record mstate := {
      regs : vec registerMap threads;
      mem : memoryMap;

      
      buf : vec (list buffer_entry) threads;

      
      lock : option (fin threads);

      
      memWritten : gset address;

      
      termThreads : vec bool threads;
    }.

Helper functions

Register functions


  Definition read_reg (tid : fin threads) (reg : reg) (state : mstate) :
      option (reg_type reg) :=
    let regMap := (regs state !!! tid) in
    dmap_lookup reg regMap.

  Definition write_reg (tid : fin threads) (reg : reg) (val : reg_type reg)
      (state : mstate) : mstate :=
    set (lookup_total tid regs) (dmap_insert reg val) state.

Buffer functions


  Definition no_pending (x : address) (tid : fin threads)
      (state : mstate) : bool :=
    let buffer := buf state !!! tid in
    bool_decide ( av buffer, addr av x).

  Definition buffer_empty (tid : fin threads) (m : mstate) : bool :=
    if buf m !!! tid is [] then true else false.

  Definition all_buffers_empty (state : mstate) : bool :=
    bool_decide ( t : fin threads, buffer_empty t state).

  Fixpoint read_byte_from_write_buffer_inner (rev_buffer : list buffer_entry)
      (goal_addr: address) :
      Exec.t mstate string (option (bv 8)) :=
    
    match rev_buffer with
    | x :: xs
      
      let index := Z.to_N (bv_unsigned (goal_addr - (addr x))%bv) in
      if bool_decide (index < size x)%N then
        mret (Some (bv_extract (8 × index) 8 (val x)))
      
      else read_byte_from_write_buffer_inner xs goal_addr
    | _mret None
    end.

  Definition read_byte_from_write_buffer (tid : fin threads) (addr : address) :
      Exec.t mstate string (option (bv 8)) :=
    buffer mget ((.!!! tid) buf);
    
    read_byte_from_write_buffer_inner (rev buffer) addr.

  Fixpoint add_to_mem_written (addr : address) (size : nat) :
      Exec.t mstate string unit :=
    match size with
    | S size
        
        mset memWritten (.∪{[addr]});;
        
        add_to_mem_written (addr `+Z` 1)%bv size
    | _mret ()
    end.

  Definition add_to_write_buffer (tid : fin threads) (addr : address)
      (size : N) (val : bv (8 × size)) (state : mstate) : mstate :=
    set ((.!!! tid) buf) (.++ [{| addr := addr; size := size; val := val |}])
      state.

Memory functions


  Definition mem_addr_modified (addr : address) (size : N) (state : mstate) : bool :=
    
    bool_decide ( a addr_range addr size, a memWritten state).

  Definition write_mem (addr : address) (size : N) (val : bv (8 × size)) :
      Exec.t mstate string unit :=
    
    opt mget (mem_lookup addr size mem);
    guard_or "Memory isn't mapped to write" (is_Some opt);;
    
    mset mem (mem_insert addr size val).

Buffer and Memory functions


  Definition read_mem_byte_with_store_forwarding (tid : fin threads) (addr : address) :
      Exec.t mstate string (bv 8) :=
    
    opt read_byte_from_write_buffer tid addr;
    if opt is Some read then
      mret read
    else
      
      opt mget (mem_lookup_byte addr mem);
      read othrow ("Memory not found at " ++ pretty addr)%string opt;
      mret read.

  Definition read_mem_with_store_forwarding (tid : fin threads) (addr : address)
      (size : N) : Exec.t mstate string (bv (8 × size)) :=
      
      read mapM
        (fun addrread_mem_byte_with_store_forwarding tid addr)
        (addr_range addr size);
      mret (bv_of_bytes (8 × size) read).

  Fixpoint write_buffer_to_mem (buffer: list buffer_entry) (tid: fin threads) :
      Exec.t mstate string unit :=
    match buffer with
    | []mret ()
    | h :: t
        write_mem (addr h) (size h) (val h);;
        write_buffer_to_mem t tid
    end.

  Definition empty_write_buffer (tid : fin threads) : Exec.t mstate string unit :=
    buffer mget ((.!!! tid) buf);
    
    write_buffer_to_mem buffer tid;;
    
    msetv ((.!!! tid) buf) [].

Lock functions


  Definition blocked (tid : fin threads) (m : mstate) : bool :=
    if lock m is Some tid' then bool_decide (tid tid')
    else false.

  Definition thread_has_lock (tid : fin threads) (m : mstate) : bool :=
    if lock m is Some tid' then bool_decide (tid = tid')
    else false.

  Definition acquire_lock (tid : fin threads) (state : mstate) : mstate :=
    setv lock (Some tid) state.

  Definition acquire_lock_conditional (tid : fin threads) :
      Exec.t mstate string unit :=
    
    lock_status mget lock;
    guard_discard (lock_status = None);;
    
    '(buffer_is_empty : bool) mget (buffer_empty tid);
    guard_discard buffer_is_empty;;
    
    mSet (acquire_lock tid).

  Definition release_lock (tid : fin threads) (state : mstate) : mstate :=
    setv lock None state.

  Definition release_lock_conditional (tid : fin threads) :
      Exec.t mstate string unit :=
    
    state mGet;
    guard_discard (thread_has_lock tid state);;
    
    '(buffer_is_empty : bool) mget (buffer_empty tid);
    guard_discard buffer_is_empty;;
    
    mSet (release_lock tid).

Model transitions

Run outcomes


  Section RunOutcome.
    Context (tid : fin threads) (eager : bool).

    Equations run_outcome (call : outcome) : Exec.t mstate string (eff_ret call) :=
    | RegRead reg racc
        opt mget (read_reg tid reg);
        othrow ("Register " ++ pretty reg ++ " not found")%string opt
    | RegWrite reg racc val
        opt mget (read_reg tid reg);
        guard_or ("Writing register " ++ pretty reg ++ " not in initial state")%string (is_Some opt);;
        mSet (write_reg tid reg val)
    | MemRead (MemReq.make macc addr () size 0)
        
        if is_ifetch macc then
          
          modified mget (mem_addr_modified addr size);
          guard_or "IFetch reading from modified memory" (negb modified);;
          
          opt mget (mem_lookup addr size mem);
          read othrow ("Memory not found at " ++ pretty addr)%string opt;
          mret (Ok (read, 0%bv))
        else if is_explicit macc then
          
          guard_discard (negb eager);;
          
          is_blocked mget (blocked tid);
          guard_discard (negb is_blocked);;
          
          (if is_atomic_rmw macc then acquire_lock_conditional tid else mret ());;
          
          read read_mem_with_store_forwarding tid addr size;
          mret (Ok (read, bv_0 _))
        else
          mthrow "Memory access type not supported"
    | MemRead _mthrow "Unsupported MemRead"
    | MemWrite (MemReq.make macc addr () size 0) val _
        
        add_to_mem_written addr (N.to_nat size);;
        
        mSet (add_to_write_buffer tid addr size val);;
        
        (if is_atomic_rmw macc then
          if eager then
            
            mdiscard
          else
            empty_write_buffer tid;;
            release_lock_conditional tid
        else mret ());;
        mret (Ok ())
    | MemWrite _ _ _mthrow "Unsupported MemWrite"
    | Barrier Barrier_MFENCE
        
        guard_discard (negb eager);;
        
        '(buffer_is_empty : bool) mget (buffer_empty tid);
        guard_discard buffer_is_empty;;
        mret ()
    | Barrier _mret ()
    | GenericFail msgmthrow msg
    | _mthrow "Unsupported outcome".
  End RunOutcome.

Flushing transition

  Definition flush_one_item_buffer (tid : fin threads) :
      Exec.t mstate string unit :=
    buffer mget ((.!!! tid) buf);
    match buffer with
    | []mdiscard
    | h :: t
        
        write_mem (addr h) (size h) (val h);;
        
        msetv ((.!!! tid) buf) t
    end.

  Context (isem : iMon ()).
  Section steps.
  Context (term : terminationCondition threads).

Execution step transition

  Definition execution_step (tid : fin threads) (eager : bool)
    : Exec.t mstate string () :=
    
    terminated mget ((.!!! tid) termThreads);
    guard_discard (negb terminated);;

    
    cinterp (run_outcome tid eager) isem;;
    
    'regs mget ((.!!! tid) regs);
    if term tid regs then
      
      msetv ((.!!! tid) termThreads) true
    else
      mret ().

Top level transitions

This returns Some tid when taking a instruction transition because it might enable eager transitions
  Definition step : Exec.t mstate string (option (fin threads)) :=
    
    tid mchoosef (fin threads);
    flush_transition mchoosef bool;
    if (flush_transition : bool) then
      
      lock_status mget lock;
      guard_discard (lock_status = None);;
      
      flush_one_item_buffer tid;;
      mret None
    else
      execution_step tid false;;
      mret (Some tid).

Eager transition functions

Run an eager transition
  Definition run_eager_thread_step (tid : fin threads) :
      Exec.t mstate string bool :=
    
    '(terminated : bool) mget ((.!!! tid) termThreads);
    if terminated then
      mret false
    else
      st mGet;
      
      let new_outcome := execution_step tid true st in
        if Exec.to_result_list new_outcome is [] then
          mret false
        else
          Exec.lift_res_st new_outcome;;
          mret true.

Runs all possible eager transition in a given thread tid. Returns the remaining fuel
  Fixpoint run_eager_thread (fuel : nat) (tid : fin threads) :
      Exec.t mstate string nat :=
    
    if fuel is S fuel then
      
      '(instr_ran : bool) run_eager_thread_step tid;
      if instr_ran then
        run_eager_thread fuel tid
      else mret (S fuel)
    else mthrow "Out of fuel".

Runs all possible transition in all threads.
  Definition run_eager_all (fuel : nat) : Exec.t mstate string nat :=
    
    foldlM run_eager_thread fuel (enum (fin threads)).

Run a non-eager step and then as many eager steps as possible, assuming no eager step could be taken before the normal step
  Definition run_normal_then_eager (fuel : nat) : Exec.t mstate string () :=
    eager_thread step;
    if eager_thread is Some tid then
      run_eager_thread fuel tid;;
      mret ()
    else mret ().

Lift to executable archModel


  Definition from_archState (astate : archState threads) : mstate :=
    {|
      regs := astate.(archState.regs);
      mem := astate.(archState.memory);
      buf := Vector.const [] threads;
      lock := None;
      memWritten := ;
      termThreads := vimap term astate.(archState.regs);
    |}.

  Definition to_archState (mstate : mstate) : option (archState threads) :=
    if all_buffers_empty mstate && bool_decide (lock mstate = None) then
      Some {|
          archState.regs := regs mstate;
          archState.memory := mem mstate;
          archState.address_space := ()
        |}
    else None.

  Definition to_terminated_archState (mstate : mstate) :
      option {s : archState threads & archState.is_terminated term s} :=
    guard ( tid, mstate.(termThreads) !!! tid : bool);;
    astate (to_archState mstate);
    if decide (archState.is_terminated term astate) is left p then
      Some (existT astate p)
    else None.
  End steps.

The unoptimised X86-TSO model. Take one step per instruction or flushing transitions. Need fuel for all instruction + all flushed writes + 1 for the terminating step
  Definition x86_tso_opmodel : opModel threads :=
    let opstep term _ _ :=
      fstate mget (to_terminated_archState term);
      if fstate is Some fs then mret (Some fs) else
        step term;;
        mret None
    in
    opModel.Make threads mstate from_archState opstep.

The X86-TSO model with eager steps. The fuel of of the previous model plus one is guaranteed to be sufficient but some lower fuel might work depending on the interleaving of eager and non-eager steps.
  Definition x86_tso_opmodel_eager : opModel threads :=
    let init term initSt := (from_archState term initSt, true) in
    let step term _ fuel :=
      fstate mget (to_terminated_archState term fst);
      if fstate is Some fs then mret (Some fs)
      else
        initial mget snd;
        if (initial : bool) then
          Exec.liftSt fst $ run_eager_all term fuel;;
          msetv snd false;;
          mret None
        else
          Exec.liftSt fst $ run_normal_then_eager term fuel;;
          mret None
    in
    opModel.Make threads (mstate × bool) init step.

End Model.
Arguments mstate : clear implicits.
Arguments x86_tso_opmodel : clear implicits.
Arguments x86_tso_opmodel_eager : clear implicits.

Top-level one-threaded model function that takes fuel (guaranteed termination) and an instruction monad, and returns a computational set of all possible final states.