Commit 6f4a7fbb authored by Kunshan Wang's avatar Kunshan Wang

Defined memory operations in one place. Added concrete description of

memory accesses via pointers.
parent 73baed17
......@@ -2029,7 +2029,7 @@ loc
excClause
*exception clause*: Used to handle NULL reference errors.
return value
Type is the strong variant of *T*: The value loaded from *loc*
Type is the strong variant of *T*: The result of the load operation.
+------+------+------+-----+------+-----------+
| opct | opct | opct | idt | idt | excClause |
......@@ -2037,11 +2037,10 @@ return value
| 0x1A | PTR | ord | T | loc | excClause |
+------+------+------+-----+------+-----------+
The ``LOAD`` instruction loads from the memory location/address *loc*.
The ``LOAD`` instruction performs a *load* operation with argument (*ord*, *T*,
*loc*) as defined in `Mu and the Memory <uvm-memory>`__.
*ord* is the memory order. In the text form, the *ord* can be omitted and
defaults to ``NOT_ATOMIC``. The instruction is atomic if *ord* is not
``NOT_ATOMIC``.
In the text form, *ord* can be omitted and defaults to ``NOT_ATOMIC``.
For LLVM users: This is the counterpart of the ``load`` instruction.
``volatile`` is absent in Mu because memory accesses are part of the
......@@ -2084,12 +2083,10 @@ newVal
| 0x1B | PTR | ord | T | loc | newVal | excClause |
+------+------+------+-----+------+--------+-----------+
The ``STORE`` instruction stores *newVal* into the memory location/address
*loc*.
The ``STORE`` instruction performs a *store* operation with argument (*ord*,
*T*, *loc*, *newVal*) as defined in `Mu and the Memory <uvm-memory>`__.
*ord* is the memory order. In the text form, the *ord* can be omitted and
defaults to ``NOT_ATOMIC``. The instruction is atomic if *ord* is not
``NOT_ATOMIC``.
In the text form, *ord* can be omitted and defaults to ``NOT_ATOMIC``.
For LLVM users: This is the counterpart of the ``store`` instruction.
``volatile`` is absent.
......@@ -2109,14 +2106,14 @@ defaults to ``NOT_ATOMIC``. The instruction is atomic if *ord* is not
``CMPXCHG`` Instruction
~~~~~~~~~~~~~~~~~~~~~~~
``CMPXCHG`` ``PTR`` :sub:`opt` ``WEAK`` :sub:`opt` *succOrd* *failOrd* ``<`` *T*
``CMPXCHG`` ``PTR`` :sub:`opt` ``WEAK`` :sub:`opt` *ordSucc* *ordFail* ``<`` *T*
``>`` *loc* *expected* *desired* *excClause*
``PTR`` :sub:`opt`
If present, *loc* is a pointer. Otherwise it is an internal reference.
``WEAK`` :sub:`opt`
If present, the ``CMPXCHG`` operation is weak.
succOrd, failOrd
ordSucc, ordFail
*memory order*: The memory order for success and failure, respectively.
T
*type*, must be EQ-comparable: The referent type of *loc*.
......@@ -2133,35 +2130,22 @@ excClause
*exception clause*: Used to handle NULL reference errors.
return value:
Type is ``struct<U int<1>>`` where *U* is the strong variant of *T*:
A pair of the original value in the memory and whether this operation is
successful.
The result of the compare exchange operation.
+------+------+------+---------+---------+-----+------+----------+---------+-----------+
| opct | opct | opct | opct | opct | idt | idt | idt | idt | excClause |
+======+======+======+=========+=========+=====+======+==========+=========+===========+
| 0x1C | PTR | WEAK | succOrd | failOrd | T | loc | expected | desired | excClause |
| 0x1C | PTR | WEAK | ordSucc | ordFail | T | loc | expected | desired | excClause |
+------+------+------+---------+---------+-----+------+----------+---------+-----------+
``CMPXCHG`` loads the value from memory location/address *loc* and compare it
with the *expected* value. If the comparison succeeds, then store the *desired*
value to *loc*. If fails, no store operation will happen. The whole process
happens atomically.
The ``CMPXCHG`` instruction performs a *compare exchange* operation with
argument (*isWeak*, *ordSucc*, *ordFail*, *T*, *loc*, *expected*, *desired*),
where *isWeak* is true if ``WEAK`` is present, otherwise false. The operation
is defined in `Mu and the Memory <uvm-memory>`__.
A ``CMPXCHG`` instruction can be **strong** or **weak**. In the text form, it is
weak if the flag ``WEAK`` is present, otherwise it is strong. In the binary
form, it is weak if the field *weak* is 1 and it is strong if *weak* is 0.
If the instruction is strong, The comparison succeeds **if and only if** the
loaded value equals the *expected* value. If it is weak, the comparison succeeds
**only if** the loaded value equals the *expected* value and it may spuriously
fail, that is, it may fail even if the loaded value equals the *expected* value.
The return value of this instruction is a struct of two fields: The first field
is the value loaded from the memory. The second field is 1 if the comparison is
successful, or 0 otherwise.
The memory order of this instruction is *succOrd* if the comparison succeeds, or
*failOrd* otherwise.
The result is a struct. The two fields represents the *v* and *s* value as
defined in `Mu and the Memory <uvm-memory>`__. The second field is 1 for true
and 0 for false.
For LLVM users: This is the counterpart of the ``cmpxchg`` instruction.
......@@ -2224,7 +2208,8 @@ opnd
excClause
*exception clause*: Used to handle NULL reference errors.
return value
Type is the strong variant of *T*: The original value in the memory.
Type is the strong variant of *T*: The result of the atomic-x operation
where x is *op*.
+------+------+------+------+-----+------+------+-----------+
| opct | opct | opct | opct | idt | idt | idt | excClause |
......@@ -2250,45 +2235,9 @@ UMAX 0x09 int unsigned max
UMIN 0x0A int unsigned min
=========== ====== === =============
The ``ATOMICRMW`` loads the value from memory location/address *loc*, perform an
operation *op* with the loaded value as the left-hand-side operand and *opnd* as
the right-hand-side operand and store the result back to the memory
location/address *loc*. The whole process happen atomically.
All operators other than ``XCHG`` are only applicable for integer types.
``XCHG`` is allowed for any type. However, a Mu implementation may only
implement some combinations of operators and operand types according to the
requirements specified in `<portability>`__
The results of the operations are: (NOTE: the result of the operation is not the
return value of this instruction)
XCHG
The value of *opnd*.
ADD
The sum of the two operands.
SUB
The difference of the two operands.
AND
The bitwise AND of the two operands.
NAND
The bitwise NOT of the bitwise AND of the two operands.
OR
The bitwise inclusive OR of the two operands.
XOR
The bitwise exclusive OR of the two operands.
MAX
The maximum value of the two operands, considering both operand as signed.
MIN
The minimum value of the two operands, considering both operand as signed.
UMAX
The maximum value of the two operands, considering both operand as unsigned.
UMIN
The minimum value of the two operands, considering both operand as unsigned.
..
NOTE: In the C syntax, the semantic of NAND is ``~(op1 & op2)``.
The ``ATOMICRMW`` instruction performs an *atomic-x* operation with argument
(*ord*, *T*, *loc*, *opnd*), where the *x* in *atomic-x* is *op*. The operation
is defined in `Mu and the Memory <uvm-memory>`__.
..
......@@ -2317,11 +2266,9 @@ ord
| 0x1E | ord |
+------+------+
The ``FENCE`` is used to introduce happen-before edges between operations. Its
The ``FENCE`` is a fence of memory order *ord*. Its
semantic is specified in `<memory-model>`__.
*ord* is the memory order of the fence.
For LLVM users: This is the counterpart of the ``fence`` instruction.
..
......
......@@ -115,8 +115,9 @@ store
A memory store. May be atomic or not.
atomic read-modify-write
A load and (maybe conditionally) a store as one atomic action. It has both a
load and a store operation, but may have special atomic properties.
A load and (maybe conditionally) a store as one atomic action. It may
contain both a load and a store operation, but may have special atomic
properties.
fence
A fence introduces memory orders.
......
......@@ -122,48 +122,18 @@ Only a subset of types can be used as the type parameter of the ``ptr`` and the
native-safe if all of their type arguments *T1*, *T2*, ..., *T*, *F*, *V* are
native-safe.
* ``ptr<T>`` is native-safe. It requires T to be native-safe.
* ``ptr<T>`` is always native-safe. It requires T to be native-safe.
* ``funcptr<sig>`` is native-safe. It requires the return type and all parameter
types of *sig* to be native-safe.
* ``funcptr<sig>`` is always native-safe. It requires the return type and all
parameter types of *sig* to be native-safe.
* All other types are not native-safe. Specifically, they are ``ref``, ``iref``,
``weakref``, ``func``, ``thread``, ``stack``.
``weakref``, ``func``, ``thread``, ``stack`` as well as ``struct``, ``array``
or ``hybrid`` that contains them.
Exposing Mu Memory to the Native World
======================================
Memory Layout
-------------
The bytes in the address space can be interpreted as Mu values in an
implementation-dependent way. The bytes that represents a Mu value is the
**bytes representation** of that Mu value.
A memory location (in the Mu memory) can be **pinned**. In this state, it is
mapped to a contiguous region of bytes in the address space which contains the
bytes representation of the value the memory location holds. The beginning of
the memory location is mapped to the lowest address of the region. Different
components of a memory location which do not contain each other do not map to
overlapping regions in the address space.
For C programmers:
* Mu assumes 8-bit bytes.
* Mu does not have the bit-field type, but a client can implement bit-fields
using integer types and bit operations.
* Mu does not have union types. However, like C, directly casting an address
to a pointer has implementation-defined behaviours. If a Mu program
interfaces with native programs, it has to also depend on the platform.
* Unlike C, Mu operations work on SSA variables rather than memory locations
(the counterpart of objects in C).
* Mu forces the 2's complement representation, though the byte order and
alignment requirement are implementation-defined.
Pinning
-------
......@@ -190,22 +160,6 @@ his multi-set. A memory location is pinned as long as there is at least one
implemented as a thread-local buffer. In this case, if GC never happens, no
expensive atomic memory access or inter-thread synchronisation is performed.
Memory Access and Memory Model
------------------------------
TODO:
* How does the atomic/non-atomic load/store/atomicrmw operations to the
bytes in the address space relate to the atomic/non-atomic
load/store/atomicrmw operations described in the Mu memory model? How does
the modification of bytes affect the values?
* How to model the modification of bytes that spans over more than one
memory location (in the Mu memory's sense)?
* And the attempt to look into the bytes representation of references must
be forbidden.
Calling between Mu and Native Functions
=======================================
......
......@@ -852,9 +852,8 @@ The ``load`` message
- return value: A handle of the loaded value.
The ``load`` message loads from the memory location/address of ``loc``.
This operation has the memory order ``ord``.
The ``load`` message performs a load operation with arguments (*ord*, *T*,
*loc*) where *T* is the referent type of *loc*.
For JNI users: This is similar to the ``Get<type>Field`` routine.
......@@ -879,9 +878,8 @@ The ``store`` message
``newval`` must have the unmarked type of the referent type of ``loc``.
The ``store`` message stores ``newval`` into the memory location/address of ``loc``.
This operation has the memory order ``ord``.
The ``store`` message performs a *store* operation with argument (*ord*,
*T*, *loc*, *newval*) where *T* is the referent type of *loc*.
For JNI users: This is similar to the ``Set<type>Field`` routine.
......@@ -914,17 +912,11 @@ The ``cmpxchg`` message
``expected`` and ``desired`` must have the unmarked type of the referent type of
``loc``.
The ``cmpxchg`` message loads form ``loc`` and compare with ``expected``. If
successful, then store ``desired`` to ``loc``, otherwise do nothing. In both
cases, the old value ``oldval`` at memory location/address ``loc`` is returned. This
whole process happen atomically.
The ``cmpxchg`` message performs a *compare exchange* operation with
argument (*weak*, *ord_sicc*, *ord_fail*, *T*, *loc*, *expected*, *desired*),
where *T* is the referent type of *loc*.
If this operation is strong, The comparison is successful if and only if the
loaded value equals ``expected``. If weak, it may spuriously fail even if they
are equal.
This operation has the memory order ``ord_succ`` when successful and
``ord_fail`` when failed.
..
Example in Java::
......@@ -958,14 +950,9 @@ The ``atomicrmw`` message
``opnd`` must have the unmarked type of the referent type of ``loc``.
The ``atomicrmw`` message performs a binary operation ``op`` on the current
value in ``loc`` as the left-hand-side and ``opnd`` as the right-hand-side and
stores the result to ``loc``. The whole process happen atomically.
``op`` is one operation defined in the ``ATOMICRMW`` instruction. (See
`<instruction-set>`__)
This operation has the memory order ``ord``.
The ``atomicrmw`` message performs an *atomic-x* operation with argument (*ord*,
*T*, *loc*, *opnd*), where *T* is the referent type of *loc*, and the *x* in
*atomic-x* is *op*.
Example Java signature: ``Handle ClientAgent.atomicrmw(MemoryOrder ord, AtomicRMWOp op, Handle loc, Handle opnd)``
......@@ -982,9 +969,7 @@ The ``fence`` message
- return value: None
The ``fence`` is a memory fence.
This operation has the memory order ``ord``.
The ``fence`` is a memory fence of memory order ``ord``.
Example Java signature: ``void ClientAgent.fence(MemoryOrder ord)``
......
=============
The Mu Memory
=============
=================
Mu and the Memory
=================
Overview
========
......@@ -22,8 +22,11 @@ so that the native interface is easier to implement.
Basic Concepts
==============
Mu Memory
---------
There are three kinds of memory in Mu, namely the **heap memory**, the **stack
memory** and the **global memory**.
memory** and the **global memory**. **Mu memory** means one of them.
Memory is allocated in their respective **allocation units**. Every allocation
unit has a **lifetime** which begins when the allocation unit is created and
......@@ -35,14 +38,17 @@ a member (if applicable) or a component of T, it also has a memory location
which is a **member** or a **component** of the memory location L, respectively.
Memory location L1 **contains** a memory location L2 if L2 is a component of L1.
The **lifetime** of a memory location is the same as the allocation unit that
contains it.
NOTE: The "object" in the C language is the counterpart of "memory location"
in Mu. Mu does not have bit fields and a memory location is always an
"object" in C's sense. The word "object" is a synonym of "heap object" or
"garbage-collected object" in the Mu context.
In C, the word "memory location" must have scalar types, but Mu uses the
word for composite types, too.
The **lifetime** of a memory location is the same as the allocation unit that
contains it.
As implementation details, when an allocation unit is destroyed and another
allocation unit occupied the same or overlapping space as the former, they are
different allocation units. Different allocation units contain no common memory
......@@ -52,6 +58,47 @@ same object. Any memory locations within the same object remain the same.
NOTE: This means the memory of Mu is an abstraction over the memory space of
the process.
Native Memory
-------------
The **native memory** is not Mu memory. The native memory is an address space of
a sequence of bytes, each can be addressed by an integral address. The size of
the address is implementation-defined.
A region of bytes in the native memory can be interpreted as Mu values in an
implementation-dependent way. The bytes that represents a Mu value is the
**bytes representation** of that Mu value.
For C programmers: it is similar to the "object representation", but in Mu,
unless a memory location is pinned, it may not be represented in such a way.
A Mu memory location can be **pinned**. In this state, it is mapped to a
(contiguous) region of bytes in the native memory which contains the bytes
representation of the value the memory location holds. The beginning of the
memory location is mapped to the lowest address of the region. Different
components of a memory location which do not contain each other do not map to
overlapping regions in the address space.
For C programmers:
* Mu assumes 8-bit bytes.
* Mu does not have the bit-field type, but a client can implement bit-fields
using integer types and bit operations.
* Mu does not have union types. However, like C, directly casting an address
to a pointer has implementation-defined behaviours. If a Mu program
interfaces with native programs, it has to also depend on the platform.
* Unlike C, Mu operations work on SSA variables rather than memory locations
(the counterpart of objects in C).
* Mu forces the 2's complement representation, though the byte order and
alignment requirement are implementation-defined.
See `Native Interface <native-interface>`__ for details about the pinning and
unpinning operations.
Memory Allocation and Deallocation
==================================
......@@ -66,7 +113,7 @@ on-stack replacement using API messages. It is destroyed when the stack frame
containing it is destroyed.
An allocation unit in the global memory is called a **global cell**. One global
cell is created for every ``.const`` declaration in a bundle submitted to Mu.
cell is created for every ``.global`` declaration in a bundle submitted to Mu.
Global cells are never destroyed.
Initial Values
......@@ -114,51 +161,196 @@ The garbage collector may move objects.
Memory Accessing
================
To **load** means to copy data from the memory. To **store** is to copy data to
the memory. To **access** is to load or store. A load operation **sees** the
value it copied.
Memory accessing operations include **load** and **store** operations. To
**access** means to load or store. **Atomic read-modify-write** operations may
have both a load and a store operation, but may have special atomic properties.
NOTE: The concrete instructions are named in capital letters: LOAD and
STORE. The abstract operations are in lower case: load, store and
access. The CMPXCHG instruction and the ATOMICRMW instruction may perform
both a load and a store operation.
NOTE: Instructions are named in capital letters: LOAD and STORE. The
abstract operations are in lower case: load, store and access.
Memory access operations can be performed by some Mu instructions (see
`<instruction-set>`__, API messages (see `<uvm-client-interface>`__) or other
`<instruction-set>`__, API messages (see `<uvm-client-interface>`__), native
programs which accesses the pinned Mu memory, or in other
implementation-specific ways.
NOTE: Since the Mu may interoperate with external entities, including
external libraries, the operating system or other non-Mu threads, the
memory model (see `<memory-model>`__) should be extended by a concrete
implementation to make meaningful programs that include external entities.
Two memory accesses **conflict** if one stores to a memory location and the
other loads from or stores to the same memory location.
The result of Mu memory access operations are defined by the Mu memory model.
See `<memory-model>`__.
Semantics of Memory Operations
------------------------------
TODO: The semantics should be defined in one place and shared between the
instruction set and the API.
Parameters and Semantics of Memory Operations
---------------------------------------------
Generally speaking, load operations copy values from the memory and store
operations copy values into the memory. The exact results are determined by the
memory model. See `<memory-model>`__.
A **load** operation has parameters ``(ord, T, loc)``. *ord* is the memory order
of the operation. *T* is the type of *loc*, a memory location. The result is a
value of the strong variant of type *T*.
A **store** operation has parameters ``(ord, T, loc, newVal)``. *ord is the
memory order of the operation. *T* is the type of *loc*, a memory location.
*newVal* is a value whose type is the strong variant of *T*. The result is void.
A **compare exchange** operation is an atomic read-modify-write operation. Its
parameters are ``(isWeak, ordSucc, ordFail, T, loc, expected, desired)``.
*isWeak* is a Boolean parameter which indicates whether the compare exchange
operation is weak or string. *ordSucc* and *ordFail* are the memory orders of
the operation when the comparing is successful or failed. *T* is the type of the
memory location *loc*. *expected* and *desired* are values whose type is the
strong variant of *T*. The result is a pair ``(v, s)``, where *v* has the type
of the strong variant of *T*, and *s* is a Boolean value.
A compare exchange operation performs a load operation on *loc* and compares its
result with *expected*. If the comparison is successful, it performs a store
operation to location *loc* with *desired* as *newVal*.
If the operation is strong, The comparison succeeds **if and only if** the
result of load equals *expected*. If it is weak, the comparison succeeds **only
if** the result of load equals the *expected* value and it may spuriously fail,
that is, it may fail even if the loaded value equals the *expected* value.
The result *v* is the result of the initial load operation and *s* is whether
the comparison is successful or not.
An **atomic-x** operation is an atomic read-modify-write operation, where *x*
can be one of (XCHG, ADD, SUB, AND, NAND, OR, XOR, MAX, MIN, UMAX, UMIN). Its
parameters are ``(ord, T, loc, opnd)``. *ord* is the memory order of the
operation. *T* is the type of the memory location *loc*. *opnd* is a value whose
type is the strong variant of *T*. The result also has the type of the strong
variant of *T*.
An atomic-x operation performs a load operation on location *loc*. Then
according to *x*, it performs one of the binary operation below, with the result
of the load operation as the left-hand-side operand and the value *opnd* as the
right-hand-side operand. The result is:
XCHG
The value of *opnd*.
ADD
The sum of the two operands.
SUB
The difference of the two operands.
AND
The bitwise AND of the two operands.
NAND
The bitwise NOT of the bitwise AND of the two operands.
OR
The bitwise inclusive OR of the two operands.
XOR
The bitwise exclusive OR of the two operands.
MAX
The maximum value of the two operands, considering both operand as signed.
MIN
The minimum value of the two operands, considering both operand as signed.
UMAX
The maximum value of the two operands, considering both operand as unsigned.
UMIN
The minimum value of the two operands, considering both operand as unsigned.
..
NOTE: In the C syntax, the semantic of NAND is ``~(op1 & op2)``.
Then it performs a store operation to location *loc* with the result of the
binary operation as *newVal*.
The result of the atomic-x operation is the result of the initial load
operation.
All operators other than ``XCHG`` are only applicable for integer types.
``XCHG`` is allowed for any type. However, a Mu implementation may only
implement some combinations of operators and operand types according to the
requirements specified in `<portability>`__
Memory Operations on Pointers
-----------------------------
Load, store, compare exchange and atomic-x operations can work with native
memory in addition to Mu memory locations. In this case, the *loc* parameter of
the above operations become a region of bytes in the native memory (usually
represented as ``ptr<T>``) rather than memory locations (usually represented as
``iref<T>``).
Only *native-safe* types can be accessed via pointers.
When accessing the memory via pointers, if the bytes are mapped to a Mu memory
location via pinning (see `Native Interface <native-interface>`__), then if the
referent type of the pointer is the same as the Mu memory location, it has the
same effect as accessing the corresponding Mu memory location.
When non-atomically loading from or storing to a region *R* of bytes which is
1. not mapped to (i.e. not perfectly overlapping with) a particular Mu memory
location, and
2. each byte in the region is part of any mapped byte region of any pinned Mu
memory location,
then such an operation loads or stores on a byte-by-byte basis. Specifically:
* Such a load operation *L*:
1. for each address *A* of byte in the region *R*, performs a load operation
on the (only) Mu memory location of scalar types (not composite types)
whose mapped byte region *R2* contains address *A*, then extract the byte
value *b* at address *A*, then
2. combine all results *b* from the previous step into a sequence of byte
values, then interprets it as the bytes representation of a Mu value.
This Mu value is the result of the load operation *L*.
* Such a store operation *S*:
1. interprets its *newVal* argument as its bytes representation *B*, then
2. for each address *A* of byte in the region *R*, performs a load operation
on the (only) Mu memory location of scalar types (not composite types)
whose mapped byte region *R2* contains address *A*, then update the
result by replacing the byte at address *A* with the byte in *B*, then
perform a store operation on the same Mu memory location with the updated
value as *newVal*.
..
NOTE: This allows Mu to allocate a byte array and access (by itself or by
native programs) it via pointers as if it is a struct or a union, and then
interpret the written values as bytes. The requirement of each byte being
mapped gives implementation-defined behaviours to accesses beyond the border
of any Mu objects (such as array out-of-bound errors), or accessing padding
bytes in Mu structs.
Accessing native memory regions not mapped to Mu memory locations has
implementation-defined behaviours.
NOTE: Accessing the native memory may have all kinds of results: getting a
previously-stored value, storing to one address and affect another address
when two addresses are mapped to the same physical memory region/file,
segmentation fault, bus error (especially on OSX), turning on/off the light
by doing memory-mapped IO, launching nuclear missiles, summoning nasal
demons, etc. Mu cannot make much guarantee.
Native programs can access pinned Mu memory locations in implementation-defined
ways.
NOTE: This means it requires the efforts from the implementations of both Mu
and the native programs to obtain any defined semantics in mixed Mu-native
programs. For C, it will involve the C language, the platform ABI and the Mu
ABI of that platform.
Memory Layout
=============
The way how data of any type are represented in the memory is
implementation-defined.
Whether or how Mu data of any type are represented in the native memory is
implementation-defined. When an object is pinned, the layout is viewed from the
native memory in a platform-dependent way.
For Mu implementers, it is recommended to use the layout defined by the
application binary interface of the platform in order to allow data exchange via
the native interface implementation.
application binary interface of the platform in order to ease the data exchange
via the native interface implementation.
Mu has some reference rules which may affect the definition of the memory
layout.
Mu has some rules about Mu memory locations which must always preserved.
Reference Rules
===============
Rules of Memory Locations
=========================
Every memory location has an associated type bound when the memory location is
created and cannot be changed. The memory location can only hold values of that
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment