Skip to content

Commit

Permalink
Implement a basic ‘toy’ STM
Browse files Browse the repository at this point in the history
The general idea is that all accesses to objects and arrays are mediated via
`Change` objects. These keep a copy of the initial state, and a working copy on
which the transactions perform their accesses. On commit, we simply compare the
initial state with the publicly visible objects, if any change (conflict) is
seen the transaction retries. If no conflict was determined, the new state is
copied into the publicly visible object.

Transactions always succeed, this implementation automatically retries.

The initial creation of a `Change` object accesses the public object while
holding its lock. Similarly, on writing back changes, the lock is acquired.
Transaction commits are globally sequentialized on a single lock.

Method and blocks executed inside a transaction execute an `atomic` variant of
their method body. All invocable used in the transactional context, i.e.,
within a `atomic: [...]` execution are split from the normal methods, and
marked as `isAtomic`. This flag allows to insert the necessary nodes to ensure
that a transaction works on working copies private to the transaction.

To avoid issues with class identity, the slots storing lazily allocated class
objects are ignored when comparing and updating objects.

Inspired by: Transactional Memory for Smalltalk
L. Renggli, and O. Nierstrasz. In Proc. of ICDL, 2007.
DOI: 10.1145/1352678.1352692

Signed-off-by: Stefan Marr <git@stefan-marr.de>
  • Loading branch information
smarr committed Jan 19, 2017
1 parent 76bcdba commit bbdd7de
Show file tree
Hide file tree
Showing 22 changed files with 707 additions and 34 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -32,6 +32,8 @@

- Added CSP version of PingPong benchmark.

- Added simple STM implementation. See `s.i.t.Transactions` and [PR #81](https://github.com/smarr/SOMns/pull/81) for details.

## 0.1.0 - 2016-12-15

This is the first tagged version. For previous changes, please refer to the
Expand Down
3 changes: 1 addition & 2 deletions core-lib/Transactions.som
Expand Up @@ -12,8 +12,7 @@ class Transactions usingVmMirror: vmMirror usingKernel: kernel = Value (
The execution of the block is retried until it succeeds without conflicts.
The result of the block is returned. *)
public atomic: block = (
(* ^ vmMirror txAtomic: block *)
^ block value
^ vmMirror txAtomic: block
)
)
)
2 changes: 1 addition & 1 deletion src/som/compiler/MethodBuilder.java
Expand Up @@ -308,7 +308,7 @@ private Method assembleInvokable(ExpressionNode body, final MethodScope scope,

Method truffleMethod = new Method(getMethodIdentifier(),
sourceSection, definition.toArray(new SourceSection[0]),
body, scope, (ExpressionNode) body.deepCopy(), blockMethod);
body, scope, (ExpressionNode) body.deepCopy(), blockMethod, false);
scope.setMethod(truffleMethod);
return truffleMethod;
}
Expand Down
25 changes: 19 additions & 6 deletions src/som/compiler/MixinDefinition.java
Expand Up @@ -41,6 +41,8 @@
import som.interpreter.objectstorage.FieldWriteNode;
import som.interpreter.objectstorage.FieldWriteNode.AbstractFieldWriteNode;
import som.interpreter.objectstorage.InitializerFieldWrite;
import som.interpreter.transactions.CachedTxSlotRead;
import som.interpreter.transactions.CachedTxSlotWrite;
import som.vm.Symbols;
import som.vm.constants.Classes;
import som.vm.constants.Nil;
Expand Down Expand Up @@ -509,11 +511,17 @@ public String toString() {

@Override
public AbstractDispatchNode getDispatchNode(final Object receiver,
final Object firstArg, final AbstractDispatchNode next) {
final Object firstArg, final AbstractDispatchNode next,
final boolean forAtomic) {
SObject rcvr = (SObject) receiver;
if (rcvr instanceof SMutableObject) {
return new CachedMutableSlotRead(getAccessType(),
createNode(rcvr), DispatchGuard.create(rcvr), next);
if (forAtomic && getAccessType() == SlotAccess.FIELD_READ) {
return new CachedTxSlotRead(getAccessType(),
createNode(rcvr), DispatchGuard.create(rcvr), next);
} else {
return new CachedMutableSlotRead(getAccessType(),
createNode(rcvr), DispatchGuard.create(rcvr), next);
}
} else {
assert rcvr instanceof SImmutableObject;
return new CachedImmutableSlotRead(getAccessType(),
Expand Down Expand Up @@ -571,9 +579,14 @@ public SlotMutator(final SSymbol name, final AccessModifier acccessModifier,

@Override
public AbstractDispatchNode getDispatchNode(final Object rcvr,
final Object firstArg, final AbstractDispatchNode next) {
return new CachedSlotWrite(createWriteNode((SObject) rcvr, firstArg),
DispatchGuard.create(rcvr), next);
final Object firstArg, final AbstractDispatchNode next, final boolean forAtomic) {
if (forAtomic) {
return new CachedTxSlotWrite(createWriteNode((SObject) rcvr, firstArg),
DispatchGuard.create(rcvr), next);
} else {
return new CachedSlotWrite(createWriteNode((SObject) rcvr, firstArg),
DispatchGuard.create(rcvr), next);
}
}

@Override
Expand Down
18 changes: 17 additions & 1 deletion src/som/interpreter/Invokable.java
Expand Up @@ -15,6 +15,9 @@
public abstract class Invokable extends RootNode {
protected final String name;

/** Marks this invokable as being used in a transactional context. */
protected final boolean isAtomic;

@Child protected ExpressionNode expressionOrSequence;

protected final ExpressionNode uninitializedBody;
Expand All @@ -23,25 +26,38 @@ public Invokable(final String name,
final SourceSection sourceSection,
final FrameDescriptor frameDescriptor,
final ExpressionNode expressionOrSequence,
final ExpressionNode uninitialized) {
final ExpressionNode uninitialized,
final boolean isAtomic) {
super(SomLanguage.class, sourceSection, frameDescriptor);
this.name = name;
this.expressionOrSequence = expressionOrSequence;
this.uninitializedBody = uninitialized;
this.isAtomic = isAtomic;
}

@Override
public String getName() {
return name;
}

public final boolean isAtomic() {
return isAtomic;
}

@Override
public Object execute(final VirtualFrame frame) {
return expressionOrSequence.executeGeneric(frame);
}

/** Inline invokable into the lexical context of the given builder. */
public abstract ExpressionNode inline(final MethodBuilder builder, SInvokable outer);

/**
* Create a version of the invokable that can be used in a
* transactional context.
*/
public abstract Invokable createAtomic();

@Override
public final boolean isCloningAllowed() {
return true;
Expand Down
21 changes: 18 additions & 3 deletions src/som/interpreter/Method.java
Expand Up @@ -43,9 +43,10 @@ public Method(final String name, final SourceSection sourceSection,
final SourceSection[] definition,
final ExpressionNode expressions,
final MethodScope methodScope,
final ExpressionNode uninitialized, final boolean block) {
final ExpressionNode uninitialized, final boolean block,
final boolean isAtomic) {
super(name, sourceSection, methodScope.getFrameDescriptor(),
expressions, uninitialized);
expressions, uninitialized, isAtomic);
this.definition = definition;
this.block = block;
this.methodScope = methodScope;
Expand Down Expand Up @@ -105,7 +106,7 @@ public Method cloneAndAdaptAfterScopeChange(final MethodScope adaptedScope,
}

Method clone = new Method(name, getSourceSection(), definition, adaptedBody,
adaptedScope, uninit, block);
adaptedScope, uninit, block, isAtomic);
adaptedScope.setMethod(clone);
return clone;
}
Expand All @@ -118,6 +119,20 @@ public Node deepCopy() {
return cloneAndAdaptAfterScopeChange(splitScope, 0, false);
}

@Override
public Invokable createAtomic() {
assert !isAtomic : "We should only ask non-atomic invokables for their atomic version";

MethodScope splitScope = methodScope.split();
ExpressionNode body = InliningVisitor.doInline(uninitializedBody, splitScope, 0);
ExpressionNode uninit = NodeUtil.cloneNode(body);

Method atomic = new Method(name, getSourceSection(), definition, body,
splitScope, uninit, block, true);
splitScope.setMethod(atomic);
return atomic;
}

public boolean isBlock() {
return block;
}
Expand Down
15 changes: 12 additions & 3 deletions src/som/interpreter/Primitive.java
Expand Up @@ -19,8 +19,8 @@ public final class Primitive extends Invokable {

public Primitive(final String name, final ExpressionNode primitive,
final FrameDescriptor frameDescriptor,
final ExpressionNode uninitialized) {
super(name, null, frameDescriptor, primitive, uninitialized);
final ExpressionNode uninitialized, final boolean isAtomic) {
super(name, null, frameDescriptor, primitive, uninitialized, isAtomic);
}

@Override
Expand All @@ -35,7 +35,16 @@ public ExpressionNode inline(final MethodBuilder builder, final SInvokable outer
public Node deepCopy() {
assert getFrameDescriptor().getSize() == 0;
return new Primitive(name, NodeUtil.cloneNode(uninitializedBody),
getFrameDescriptor(), uninitializedBody);
getFrameDescriptor(), uninitializedBody, isAtomic);
}

@Override
public Invokable createAtomic() {
assert !isAtomic : "We should only ask non-atomic invokables for their atomic version";
ExpressionNode atomic = NodeUtil.cloneNode(uninitializedBody);
ExpressionNode uninitAtomic = NodeUtil.cloneNode(atomic);

return new Primitive(name, atomic, getFrameDescriptor(), uninitAtomic, true);
}

@Override
Expand Down
8 changes: 4 additions & 4 deletions src/som/interpreter/nodes/dispatch/CachedSlotAccessNode.java
Expand Up @@ -105,11 +105,11 @@ protected Object read(final VirtualFrame frame, final Object rcvr) throws Invali
}
}

public static final class CachedSlotWrite extends AbstractDispatchNode {
public static class CachedSlotWrite extends AbstractDispatchNode {
@Child protected AbstractDispatchNode nextInCache;
@Child protected AbstractFieldWriteNode write;

private final DispatchGuard guard;
protected final DispatchGuard guard;

public CachedSlotWrite(final AbstractFieldWriteNode write,
final DispatchGuard guard,
Expand All @@ -136,7 +136,7 @@ public Object executeDispatch(final VirtualFrame frame,
}

@Override
protected boolean isTaggedWith(final Class<?> tag) {
protected final boolean isTaggedWith(final Class<?> tag) {
if (tag == FieldWrite.class) {
return true;
} else {
Expand All @@ -145,7 +145,7 @@ protected boolean isTaggedWith(final Class<?> tag) {
}

@Override
public int lengthOfDispatchChain() {
public final int lengthOfDispatchChain() {
return 1 + nextInCache.lengthOfDispatchChain();
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/som/interpreter/nodes/dispatch/Dispatchable.java
@@ -1,18 +1,19 @@
package som.interpreter.nodes.dispatch;

import som.compiler.AccessModifier;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.IndirectCallNode;

import som.compiler.AccessModifier;


/**
* Something that can create a dispatch node.
* Used for slots, and methods currently.
*/
public interface Dispatchable {

AbstractDispatchNode getDispatchNode(Object rcvr, Object firstArg, AbstractDispatchNode newChainEnd);
AbstractDispatchNode getDispatchNode(
Object rcvr, Object firstArg, AbstractDispatchNode newChainEnd, boolean forAtomic);

AccessModifier getAccessModifier();
Object invoke(IndirectCallNode node, VirtualFrame frame, Object... arguments);
Expand Down
Expand Up @@ -5,11 +5,13 @@
import com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;

import som.VM;
import som.compiler.AccessModifier;
import som.compiler.MixinBuilder.MixinDefinitionId;
import som.interpreter.Invokable;
import som.interpreter.TruffleCompiler;
import som.interpreter.Types;
import som.interpreter.nodes.ISuperReadNode;
Expand Down Expand Up @@ -89,11 +91,24 @@ private AbstractDispatchNode createSomDispatchNode(final Object rcvr,
node = new CachedDnuNode(rcvrClass, selector,
DispatchGuard.create(rcvr), newChainEnd);
} else {
node = dispatchable.getDispatchNode(rcvr, firstArg, newChainEnd);
node = dispatchable.getDispatchNode(rcvr, firstArg, newChainEnd, forAtomic());
}
return node;
}

private boolean forAtomic() {
// TODO: seems a bit expensive,
// might want to optimize for interpreter first iteration speed
RootNode root = getRootNode();
if (root instanceof Invokable) {
return ((Invokable) root).isAtomic();
} else {
// TODO: need to think about integration with actors, but, that's a
// later research project
return false;
}
}

protected AbstractDispatchNode generalizeChain(
final GenericMessageSendNode sendNode) {
// the chain is longer than the maximum defined by INLINE_CACHE_SIZE and
Expand Down
26 changes: 26 additions & 0 deletions src/som/interpreter/transactions/CachedTxSlotRead.java
@@ -0,0 +1,26 @@
package som.interpreter.transactions;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.InvalidAssumptionException;

import som.interpreter.nodes.dispatch.AbstractDispatchNode;
import som.interpreter.nodes.dispatch.CachedSlotAccessNode.CachedSlotRead;
import som.interpreter.nodes.dispatch.DispatchGuard;
import som.interpreter.objectstorage.FieldReadNode;
import som.vmobjects.SObject.SMutableObject;


public final class CachedTxSlotRead extends CachedSlotRead {
public CachedTxSlotRead(final SlotAccess type,
final FieldReadNode read,
final DispatchGuard guard, final AbstractDispatchNode nextInCache) {
super(type, read, guard, nextInCache);
assert type == SlotAccess.FIELD_READ;
}

@Override
protected Object read(final VirtualFrame frame, final Object rcvr) throws InvalidAssumptionException {
SMutableObject workingCopy = Transactions.workingCopy((SMutableObject) rcvr);
return read.read(frame, workingCopy);
}
}
38 changes: 38 additions & 0 deletions src/som/interpreter/transactions/CachedTxSlotWrite.java
@@ -0,0 +1,38 @@
package som.interpreter.transactions;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.InvalidAssumptionException;

import som.interpreter.nodes.dispatch.AbstractDispatchNode;
import som.interpreter.nodes.dispatch.CachedSlotAccessNode.CachedSlotWrite;
import som.interpreter.nodes.dispatch.DispatchGuard;
import som.interpreter.objectstorage.FieldWriteNode.AbstractFieldWriteNode;
import som.vmobjects.SObject.SMutableObject;


public final class CachedTxSlotWrite extends CachedSlotWrite {
public CachedTxSlotWrite(final AbstractFieldWriteNode write,
final DispatchGuard guard,
final AbstractDispatchNode nextInCache) {
super(write, guard, nextInCache);

}

@Override
public Object executeDispatch(final VirtualFrame frame,
final Object[] arguments) {
Object rcvr = arguments[0];
try {
if (guard.entryMatches(rcvr)) {
SMutableObject workingCopy = Transactions.workingCopy((SMutableObject) rcvr);
return write.write(workingCopy, arguments[1]);
} else {
return nextInCache.executeDispatch(frame, arguments);
}
} catch (InvalidAssumptionException e) {
CompilerDirectives.transferToInterpreterAndInvalidate();
return replace(nextInCache).executeDispatch(frame, arguments);
}
}
}

0 comments on commit bbdd7de

Please sign in to comment.