Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Branches in atomic sections, and simplified atomic syntax #142

Merged
merged 18 commits into from
Mar 13, 2017

Conversation

MattWindsor91
Copy link
Collaborator

This pull request contains three large changes to Starling's syntax and semantics:

  1. Simplify atomic syntax
  2. Allow branches in atomic sections (issue Conditionals in atomic sections #141)
  3. Rewrite microcode transformation pipeline (touches on issue Aliasing problem with indices #114)

Simplify atomic syntax

Previously, a Starling atomic could be written as <cmd>; if it was one command, or <{cmd1; cmd2; cmdN;}>; if it contained more than one.

This seemed somewhat wasteful and confusing, and mainly happened because the second was an afterthought. This pull request removes the first form and slightly changes the second form, so that atomic commands are now always in the form

<| cmd1; cmd2; cmdN; |> // note the lack of outer semicolon at the end

All of the Pass, Fail, PassGH, and FailGH examples have been rewritten. (None of the WIP ones have, yet.)

Allow branches in atomic sections

Atomic blocks can now contain the form

<| /* ... */ if (condition) { /* atomic block */ } else { /* atomic block */ } /* ... */ |>

which atomically evaluates the conditional and then the appropriate atomic block.

This allows us to write a pure-Starling CAS inline. For example, the CAS in Examples/Pass/spinLock.cvf could be written as:

      {| if test == false then emp else False() |}
        <| if (lock == test) { lock = true; } test = lock; |>
      {| if test == false then holdLock() else emp |}

We can also almost write a heap CAS. This does the same thing as CAS_to_true in Examples/PassGH/spinLock.cvf, but in a slightly convoluted way (see below caveats):

      {| if test == false then isLock(x) else False() |}
        <| test = %{ [|x|].lock };
           if (%{ [|x|].lock } == false) {
               %{ [|x|].lock := true };
           } |>
        test = !test;
      {| if test == false then isLock(x) else holdLock(x) |}

Rewrite microcode translation pipeline

To allow 2) to go through, the bit of Starling that converts Microcode into marked, normalised microcode or Boolean relations needed a significant overhaul. This is because previously it assumed any microcode instructions inside a branch (or, indeed, inside a single stored command!) were disjoint, and thus could share the same Intermediate level.

Summary of the changes:

  • All microcode instructions are internally sequentially composed now, which fixes the ambiguity in Aliasing problem with indices #114 as a side-effect. Two writes to the same variable will always end up executing in the order in which they are given in the microcode;
  • Write map building has been removed, because we no longer have the situation where a single microcode write can update more than one bit of an array at the same time;
  • Intermediate variables are now only introduced on a per-variable basis when the variable is assigned, which makes for a more efficient use of variables.

Future work

  • At this stage it might be worth relaxing the restrictions on atomic assignments to allow, e.g., assignments of arbitrary expressions to thread-locals in atomic blocks. This is why we can't just write the above CAS as
      {| if test == false then isLock(x) else False() |}
        <| test = %{ [|x|].lock };
           if (%{ [|x|].lock } == false) {
               %{ [|x|].lock := true };
               test = true;  // Currently forbidden
           } else { test = false; } |>
      {| if test == false then isLock(x) else holdLock(x) |}
  • I'm considering changing the view branch syntax to if (x) { y } else { z } for consistency and language simplicity, but this would make views somewhat less readable.

Instead of having two atomic syntaxes:

```
<x = y++>;  // single atomic action
<{ x = y; y = y + 1; }>;  // multiple atomic actions
```

We now have a single syntax:

```
<| x = y++; |>  // single atomic action
<| x = y; y = y + 1; |>  // multiple atomic actions
```

Note that the new syntax behaves exactly like a block: it requires
semicolons to terminate inner actions, but has no semicolon at the
end.

This is in preparation for making atomic syntax richer.
This still needs two more changes:

1) Making microcode conditionals sequentially composed instead of
   parallel composed;
2) Exposing the syntax.
It probably fails 99.9% of tests at the moment.  Will pick up after
日本語の勉強.
@septract
Copy link
Owner

Re encoding CAS, can we write this?

        <| test = ! %{ [|x|].lock };
           if (test) {
               %{ [|x|].lock := true };
           } |>

@septract
Copy link
Owner

...but in general, it'd be sensible to be able to write an arbitrary expression to a local var. Is there a technical reason we can't do this?

@MattWindsor91
Copy link
Collaborator Author

MattWindsor91 commented Feb 16, 2017 via email

@MattWindsor91
Copy link
Collaborator Author

@septract aside from the future work, is there anything else suspect about this? It's a fairly large and boring PRQ I'm afraid.

@septract
Copy link
Owner

septract commented Feb 17, 2017 via email

@septract
Copy link
Owner

Could we look through the code together at some point?

@MattWindsor91
Copy link
Collaborator Author

MattWindsor91 commented Feb 20, 2017 via email

@MattWindsor91
Copy link
Collaborator Author

I'm now tracking the separate problem of overly strict assignment restrictions with #147.

| ACond of
cond : Expression
* trueBranch : Atomic list
* falseBranch : (Atomic list) option
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just adding if (c) { … } else { … } and if (c) { … } to the AST.

/// <summary>
/// Hidden building-blocks for AST pretty-printers.
/// </summary>
module private Helpers =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a F# feature I didn't realise existed until recently. It feels like we should have made more use of this throughout the code!

<+> (maybe
Nop
(fun e -> syntaxStr "else" <+> printBlock pLeg e)
elseLeg)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abstracted this out because it accounts both for atomic and non-atomic conditionals.

@@ -290,7 +331,7 @@ module Pretty =
/// <param name="s">The symbolic to print.</param>
/// <returns>
/// The <see cref="Doc"/> resulting from printing <paramref name="s"/>.
/// </returns>
/// </returns>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space error

braced (ivsep (List.map (pCmd >> Indent) c))
| ACond (cond = c; trueBranch = t; falseBranch = f) ->
Helpers.printITE printExpression printAtomic c t f
and printAtomic (x : Atomic) : Doc = printAtomic' x.Node
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just adding conditionals to the printer. This now needs to be recursive, because conditionals can contain atomics.

printBlock printCommand f ])
fo) ]
| Command'.If(c, t, f) ->
Helpers.printITE printExpression printCommand c t f
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the new abstraction.

@@ -84,7 +89,7 @@ module Types =
| /// <summary>
/// The given microcode command cannot be expressed in Grasshopper.
/// </summary>
CommandNotImplemented of cmd : Microcode<CTyped<MarkedVar>, Sym<MarkedVar>> list
CommandNotImplemented of cmd : Microcode<CTyped<MarkedVar>, Sym<MarkedVar>>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, things that used to be ||-composed lists of microcode are now just flattened into single microcode instructions

String "if"
<+> parened (printBoolExprG (printSymGrass printGrassVar) c)
<+> printBlock t
<+> (if f.IsEmpty then Nop else (String "else" <+> printBlock f))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As well as adding Branch, rearrange this because not every command now needs withSemi.

id)
(traverseMicrocode
collectVars
(tliftOverExpr collectSymVars))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going from list list to list.

[ printMicrocode
(printCTyped printMarkedVar)
(printSym printMarkedVar)
cmd ]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing one level of list

@@ -407,75 +417,75 @@ let findTermVars (term : Backends.Z3.Types.ZTerm)
/// <returns>
/// A Chessie result, containing a list of Grasshopper commands.
/// </returns>
let grassMicrocode (routine : Microcode<CTyped<MarkedVar>, Sym<MarkedVar>> list list)
let rec grassMicrocode (routine : Microcode<CTyped<MarkedVar>, Sym<MarkedVar>> list)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going from list list to list, and adding branches introduces recursion.

fail
(CommandNotImplemented
(cmd = ent,
why = "Cannot encode commands with inner conditionals."))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now can encode inner conditionals, see line 434 of the new version.

lift3 (fun c' t' f' -> [ Branch (c', t', f') ])
(grassBool c)
(grassMicrocode t)
(grassMicrocode f)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Branches are new, but distribute.

ok []
(* We can only translate pure assignments and fully spatial
(symbolic) assignments. *)
| Microcode.Assign (x, Some (Int (_, IVar (Sym s))))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stuff, I think, has mainly just moved and re-indented.

@@ -805,8 +810,29 @@ and modelBoolExpr
| Eq -> mkEq
| Neq -> mkNeq
| _ -> failwith "unreachable[modelBoolExpr::AnyIn]"
(* If at least one of the operands is a symbol, we need to
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new code just tries to make sure that, if one side of an equality/nonequality is a symbol, we try work out its type by modelling the other side first.

This is unrelated to atomic conditionals, I think it's wormed its way into here due to some hacking I was doing around CAV.

| SymAtomic sym ->
// TODO(CaptainHayashi): split out.
let symMR =
(tryMapSym
(wrapMessages BadExpr (modelExpr ctx.Env Full id))
sym)
lift SymC symMR
| ACond (cond = c; trueBranch = t; falseBranch = f) ->
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New code to allow modelling of atomic conditionals.

@@ -585,6 +585,10 @@ module Graph =
| // TODO(CaptainHayashi): is this correct?
Intrinsic (Havoc v) -> typedVarIsThreadLocal v
| Stored { Args = ps } -> Seq.forall isLocalArg ps
| PrimBranch (trueBranch = t; falseBranch = f) ->
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locality, I think, just distributes through branches.

/// <summary>
/// Parser for items in a pair of matching brackets.
/// Automatically parses any whitespace after `bra`, and before `ket`.
/// </summary>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of boring changes to top-level comments here, but there are important parser changes coming.

/// <summary>
/// Parser for items in <c><|atomic braces|></c>.
/// </summary>
let inAtomicBraces p = inBrackets "<|" "|>" p
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The brackets have changed!

@@ -90,6 +103,10 @@ let parseView, parseViewRef =
let parseViewSignature, parseViewSignatureRef =
createParserForwardedToRef<ViewSignature, unit> ()

/// Parser for atomic sets.
let parseAtomic, parseAtomicRef =
createParserForwardedToRef<Atomic, unit> ()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed because atomics now parse recursively.

.>>? notFollowedBy (anyOf ";+-")
(* A binary operator cannot ever be followed by + or -.
This check removes ambiguity between + and ++, and - and --. *)
.>>? notFollowedBy (anyOf "+-")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing from < > to <| |> means we lose the parser ambiguity formerly induced by < foo = x > 3 >;, so we can simplify this.

(inBraces pLeg)
(opt (pstring "else" >>. ws >>. inBraces pLeg))
ctor

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abstract out the parser logic for both atomic and non-atomic conditionals.

<|>
// ...or an atomic{} block.
(inBraces (many (parseAtomic .>> wsSemi .>> ws))))
inAtomicBraces (many1 (parseAtomic .>> ws))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new syntax knocks both of the old cases into one.

parseAssume
parseCAS
parseFetchOrPostfix ]
parseIfLike (many1 (parseAtomic .>> ws)) (curry3 ACond)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Atomic conditionals are now a possibility here.

(*
* Views.
*)

/// Parses a conditional view.
let parseIfView =
// TODO: use parseIflike.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO is optional, really. Changing to this would make view conditionals look like

{| if (x) { y } else { z} |}

which is ugly but consistent.

@@ -540,7 +558,7 @@ let parsePrimSet =
2 is easier to spot, so we try it first. *)
let parseAtomicFirstPrimSet =
pipe2
(parseAtomicSet .>> wsSemi .>> ws)
(parseAtomicSet .>> ws)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer require a ; after an atomic block.

@@ -776,6 +776,17 @@ let pushVar (ctx : TraversalContext<'Var>) (v : CTyped<'Var>)
| c -> fail (ContextMismatch ("vars context", stripVars c))

/// <summary>
/// As <see cref="pushVar"/>, but does not crash on invalid context.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was borne out of frustration mainly, and just means I can get away with not doing a lot of traversal mangling to add and remove contexts just so pushVar works.

@MattWindsor91
Copy link
Collaborator Author

@septract I've tried annotating this code with top-level explanations, mainly as cues for myself when we get to reviewing this :)

@MattWindsor91
Copy link
Collaborator Author

Also, the failing Travis build is due to travis-ci/travis-ci#7401

@MattWindsor91
Copy link
Collaborator Author

@septract it doesn't look like Travis is going to build this reliably any time soon. I'd recommend pulling and testing locally.

@septract
Copy link
Owner

This looks okay to me, although I haven't grokked the details.

@septract septract merged commit 8d854aa into master Mar 13, 2017
@septract septract deleted the mw-atomic-conditionals branch March 13, 2017 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants