The Transactional Memory Module

The Transactional Memory module provides transactional semantics when accessing main memory. You can load and store variables in main memory, or adopt memory regions into a transactions. More...


file  picotm-tm-ctypes.h
 Provides Transactional Memory operations for native C types.
file  picotm-tm.h
 Public interfaces of picotm's Transactional Memory module.

Detailed Description

The Transactional Memory module provides load and store operations for main memory. In order to avoid conflicting access to shared memory locations, picotm has to know which transaction uses which locations. The Transactional Memory module maintains these information.

A call to load_tx() copies a memory location's value into a transaction, as illustrated in the example below.

int x;
int x_tx;
load_tx(&x, &x_tx, sizeof(x));
// The value of 'x' is now stored in 'x_tx'.

This copies the value of x into our transaction's variable x_tx and puts the memory location of x under control of the transaction manager. We are free to change the value of x_tx at will, since it's transaction local. If we change it, the original non-transactional value in x remains unchanged.

In a similarly way we store a copy of a transactional variable in a memory location. This is done with store_tx().

int x;
int x_tx;
store_tx(&x, &x_tx, sizeof(x));
// The value of 'x_tx' will be committed into 'x'.

As with all transactional modifications, the store will not be executed immediately, but only become permanent after the transaction successfully committed.

We don't have to deal with all these addresses ourselves. The TM module comes with helper functions for the basic C types. With load_int_tx() and store_int_tx() we can rewrite the example transactions as shown below.

int x;
int x_tx = load_int_tx(&x);
// The value of 'x' is now stored in 'x_tx'.
store_int_tx(&x, x_tx);
// The value of 'x_tx' will be committed into 'x'.

Besides load_int_tx() and store_int_tx(), the Transactional Memory module provides similar functions for the basic C types. Each is defined via the macros PICOTM_TM_LOAD_TX() and PICOTM_TM_STORE_TX(). We can use these macros to define load and store functions for our application's data types. Both macros expand to inline C functions, so we don't loose performance compared to load_tx() and store_tx().

// An application-specific type
typedef unsigned int my_int_type;
// Define load_my_int_type_tx()
PICOTM_TM_LOAD_TX(my_int_type, my_int_type);
// Define store_my_int_type_tx()
PICOTM_TM_STORE_TX(my_int_type, my_int_type);

Since address and pointer handling can be tricky, there are also helpers for loading and storing pointers. These functions load and store the address stored in a pointer variable, but not the value stored at that address.

int* x_ptr;
int* x_ptr_tx = load_ptr_tx(&x_ptr);
// The value of 'x_ptr' is now stored in 'x_ptr_tx'.
store_ptr_tx(&x_ptr, &x_ptr_tx);
// The value of 'x_ptr_tx' will be committed into 'x_ptr'.

Loads and stores always copy values into or out of a transaction. There are cases where we don't want a copy, but the exact memory location of a value. This is called privatization. For example, the function memcpy() loads and stores an indefinite amount of data between memory buffers. The actual buffer size is often not known in advance. It would be wasteful to first load the data into a transaction-local buffer and then further store it in another buffer.

The functions privatize_tx() and privatize_c_tx() offer privatization of memory locations.

int x;
// The memory location of 'x' is now available within the transaction.
// Changes to 'x' will be committed into the memory location of 'x'.

This privatizes x for transactional access from within the transaction. The number of bytes is given in the second argument. The flags argument is a bitmask of PICOTM_TM_PRIVATIZE_LOAD and PICOTM_TM_PRIVATIZE_STORE. These flags control how picotm handles the memory location. If we only privatized a memory location for loading or storing, we may never invoke the other operation. Setting no flags at all will discard the memory location. This signals to other transactions that the memory is invalid and to be freed.

There can be cases where we don't know in advance how large in size the privatized buffer is going to be. For example, if we privatize a C string, the length is not explicitly stored in the string, but given by the location of the terminating \0 character. To privatize a memory region up to and including a specific character, there is privatize_c_tx().

char* str = "foo";
// The string at 'str' is now available within the transaction.
// Changes to 'str' will be committed into the string at 'str'.

Using privatize_c_tx() with strings is the most common use case, but arbitrary characters are possible.

Finally, there is loadstore_tx(). Even through the name suggests load-and-store, the function is a mixture of load, store and privatization. It privatizes an input buffer for loading and an output buffer for storing, and copies the input buffer's content into the output buffer.

int ibuf[20];
int obuf[20];
loadstore_tx(ibuf, obuf, sizeof(obuf));
// The data in 'ibuf' will be committed into the memory of 'obuf'.

A call to loadstore_tx() is like a call to memcpy() that privatizes its input buffers. It's an optimization for platform without transactional C library.