From e3ab75e73b1a262bc6f976ce46d571d4416c60dd Mon Sep 17 00:00:00 2001 From: Xiaoge Su Date: Sun, 18 Aug 2024 13:40:21 -0700 Subject: [PATCH 1/3] Reformat ActorCompiler C# code --- flow/actorcompiler/ActorCompiler.cs | 1203 ++++++++++++++++++++------- flow/actorcompiler/ActorParser.cs | 872 +++++++++++++------ flow/actorcompiler/ParseTree.cs | 81 +- flow/actorcompiler/Program.cs | 62 +- 4 files changed, 1610 insertions(+), 608 deletions(-) diff --git a/flow/actorcompiler/ActorCompiler.cs b/flow/actorcompiler/ActorCompiler.cs index 599f65d4c06..b158280f246 100644 --- a/flow/actorcompiler/ActorCompiler.cs +++ b/flow/actorcompiler/ActorCompiler.cs @@ -20,10 +20,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; +using System.Linq; using System.Security.Cryptography; +using System.Text; namespace actorcompiler { @@ -32,7 +32,12 @@ class TypeSwitch object value; R result; bool ok = false; - public TypeSwitch(object t) { this.value = t; } + + public TypeSwitch(object t) + { + this.value = t; + } + public TypeSwitch Case(Func f) where T : class { @@ -47,9 +52,11 @@ public TypeSwitch Case(Func f) } return this; } + public R Return() { - if (!ok) throw new Exception("Typeswitch didn't match."); + if (!ok) + throw new Exception("Typeswitch didn't match."); return result; } } @@ -60,22 +67,74 @@ class Context public Function next; public Function breakF; public Function continueF; - public Function catchFErr; // Catch function taking Error - public int tryLoopDepth = 0; // The number of (loopDepth-increasing) loops entered inside the innermost try (thus, that will be exited by a throw) + public Function catchFErr; // Catch function taking Error + public int tryLoopDepth = 0; // The number of (loopDepth-increasing) loops entered inside the innermost try (thus, that will be exited by a throw) - public void unreachable() { target = null; } + public void unreachable() + { + target = null; + } // Usually we just change the target of a context - public Context WithTarget(Function newTarget) { return new Context { target = newTarget, breakF = breakF, continueF = continueF, next = null, catchFErr = catchFErr, tryLoopDepth = tryLoopDepth }; } + public Context WithTarget(Function newTarget) + { + return new Context + { + target = newTarget, + breakF = breakF, + continueF = continueF, + next = null, + catchFErr = catchFErr, + tryLoopDepth = tryLoopDepth + }; + } // When entering a loop, we have to provide new break and continue functions - public Context LoopContext(Function newTarget, Function breakF, Function continueF, int deltaLoopDepth) { return new Context { target = newTarget, breakF = breakF, continueF = continueF, next = null, catchFErr = catchFErr, tryLoopDepth = tryLoopDepth + deltaLoopDepth }; } + public Context LoopContext( + Function newTarget, + Function breakF, + Function continueF, + int deltaLoopDepth + ) + { + return new Context + { + target = newTarget, + breakF = breakF, + continueF = continueF, + next = null, + catchFErr = catchFErr, + tryLoopDepth = tryLoopDepth + deltaLoopDepth + }; + } - public Context WithCatch(Function newCatchFErr) { return new Context { target = target, breakF = breakF, continueF = continueF, next = null, catchFErr = newCatchFErr, tryLoopDepth = 0 }; } + public Context WithCatch(Function newCatchFErr) + { + return new Context + { + target = target, + breakF = breakF, + continueF = continueF, + next = null, + catchFErr = newCatchFErr, + tryLoopDepth = 0 + }; + } - public Context Clone() { return new Context { target = target, next = next, breakF = breakF, continueF = continueF, catchFErr = catchFErr, tryLoopDepth = tryLoopDepth }; } + public Context Clone() + { + return new Context + { + target = target, + next = next, + breakF = breakF, + continueF = continueF, + catchFErr = catchFErr, + tryLoopDepth = tryLoopDepth + }; + } }; - + class Function { public string name; @@ -95,19 +154,23 @@ public Function() body = new StreamWriter(new MemoryStream()); } - public void setOverload(Function overload) { + public void setOverload(Function overload) + { this.overload = overload; } - public Function popOverload() { + public Function popOverload() + { Function result = this.overload; this.overload = null; return result; } - public void addOverload(params string[] formalParameters) { + public void addOverload(params string[] formalParameters) + { setOverload( - new Function { + new Function + { name = name, returnType = returnType, endIsUnreachable = endIsUnreachable, @@ -119,9 +182,12 @@ public void addOverload(params string[] formalParameters) { public void Indent(int change) { - for(int i=0; i0, run the actor beginning at point P until * (1) it waits, in which case set an appropriate callback and return 0, or * (2) it returns, in which case destroy the actor and return 0, or - * (3) it reaches the bottom of the Nth innermost loop containing P, in which case + * (3) it reaches the bottom of the Nth innermost loop containing P, in which case * return max(0, the given loopDepth - N) (N=0 for the innermost loop, N=1 for the next innermost, etc) - * + * * Examples: * Source: * loop @@ -196,7 +269,7 @@ public virtual string call(params string[] parameters) * [Q'] * break * [Q] - * + * * fP(1) should execute everything from [P] to [Q] and then return 1 (since [Q] is at the bottom of the 0th innermost loop containing [P]) * fP'(2) should execute everything from [P'] to [Q] and then return 1 (since [Q] is at the bottom of the 1st innermost loop containing [P']) * fP''(3) should execute everything from [P''] to [Q] and then return 1 (since [Q] is at the bottom of the 2nd innermost loop containing [P'']) @@ -207,22 +280,32 @@ public virtual string call(params string[] parameters) class LiteralBreak : Function { - public LiteralBreak() { name = "break!"; } + public LiteralBreak() + { + name = "break!"; + } + public override string call(params string[] parameters) { wasCalled = true; - if (parameters.Length != 0) throw new Exception("LiteralBreak called with parameters!"); + if (parameters.Length != 0) + throw new Exception("LiteralBreak called with parameters!"); return "break"; } }; class LiteralContinue : Function { - public LiteralContinue() { name = "continue!"; } + public LiteralContinue() + { + name = "continue!"; + } + public override string call(params string[] parameters) { wasCalled = true; - if (parameters.Length != 0) throw new Exception("LiteralContinue called with parameters!"); + if (parameters.Length != 0) + throw new Exception("LiteralContinue called with parameters!"); return "continue"; } }; @@ -247,23 +330,42 @@ public DescrCompiler(Descr descr, int braceDepth) this.descr = descr; this.memberIndentStr = new string('\t', braceDepth); } + public void Write(TextWriter writer, out int lines) { lines = 0; - writer.WriteLine(memberIndentStr + "template<> struct Descriptor {{", descr.name); - writer.WriteLine(memberIndentStr + "\tstatic StringRef typeName() {{ return \"{0}\"_sr; }}", descr.name); + writer.WriteLine( + memberIndentStr + "template<> struct Descriptor {{", + descr.name + ); + writer.WriteLine( + memberIndentStr + "\tstatic StringRef typeName() {{ return \"{0}\"_sr; }}", + descr.name + ); writer.WriteLine(memberIndentStr + "\ttypedef {0} type;", descr.name); lines += 3; foreach (var dec in descr.body) { writer.WriteLine(memberIndentStr + "\tstruct {0}Descriptor {{", dec.name); - writer.WriteLine(memberIndentStr + "\t\tstatic StringRef name() {{ return \"{0}\"_sr; }}", dec.name); - writer.WriteLine(memberIndentStr + "\t\tstatic StringRef typeName() {{ return \"{0}\"_sr; }}", dec.type); - writer.WriteLine(memberIndentStr + "\t\tstatic StringRef comment() {{ return \"{0}\"_sr; }}", dec.comment); + writer.WriteLine( + memberIndentStr + "\t\tstatic StringRef name() {{ return \"{0}\"_sr; }}", + dec.name + ); + writer.WriteLine( + memberIndentStr + "\t\tstatic StringRef typeName() {{ return \"{0}\"_sr; }}", + dec.type + ); + writer.WriteLine( + memberIndentStr + "\t\tstatic StringRef comment() {{ return \"{0}\"_sr; }}", + dec.comment + ); writer.WriteLine(memberIndentStr + "\t\ttypedef {0} type;", dec.type); - writer.WriteLine(memberIndentStr + "\t\tstatic inline type get({0}& from);", descr.name); + writer.WriteLine( + memberIndentStr + "\t\tstatic inline type get({0}& from);", + descr.name + ); writer.WriteLine(memberIndentStr + "\t};"); lines += 7; } @@ -278,17 +380,29 @@ public void Write(TextWriter writer, out int lines) FirstDesc = false; } writer.Write("> fields;\n"); - writer.WriteLine(memberIndentStr + "\ttypedef make_index_sequence_impl<0, index_sequence<>, std::tuple_size::value>::type field_indexes;"); + writer.WriteLine( + memberIndentStr + + "\ttypedef make_index_sequence_impl<0, index_sequence<>, std::tuple_size::value>::type field_indexes;" + ); writer.WriteLine(memberIndentStr + "};"); - if(descr.superClassList != null) - writer.WriteLine(memberIndentStr + "struct {0} : {1} {{", descr.name, descr.superClassList); + if (descr.superClassList != null) + writer.WriteLine( + memberIndentStr + "struct {0} : {1} {{", + descr.name, + descr.superClassList + ); else writer.WriteLine(memberIndentStr + "struct {0} {{", descr.name); lines += 4; foreach (var dec in descr.body) { - writer.WriteLine(memberIndentStr + "\t{0} {1}; //{2}", dec.type, dec.name, dec.comment); + writer.WriteLine( + memberIndentStr + "\t{0} {1}; //{2}", + dec.type, + dec.name, + dec.comment + ); lines++; } @@ -297,17 +411,24 @@ public void Write(TextWriter writer, out int lines) foreach (var dec in descr.body) { - writer.WriteLine(memberIndentStr + "{0} Descriptor<{1}>::{2}Descriptor::get({1}& from) {{ return from.{2}; }}", dec.type, descr.name, dec.name); + writer.WriteLine( + memberIndentStr + + "{0} Descriptor<{1}>::{2}Descriptor::get({1}& from) {{ return from.{2}; }}", + dec.type, + descr.name, + dec.name + ); lines++; } - } } class ActorCompiler { Actor actor; - string className, fullClassName, stateClassName; + string className, + fullClassName, + stateClassName; string sourceFile; List state; List callbacks = new List(); @@ -318,12 +439,19 @@ class ActorCompiler const string memberIndentStr = "\t"; static HashSet usedClassNames = new HashSet(); bool LineNumbersEnabled; - int chooseGroups = 0, whenCount = 0; + int chooseGroups = 0, + whenCount = 0; string This; bool generateProbes; public Dictionary<(ulong, ulong), string> uidObjects { get; private set; } - public ActorCompiler(Actor actor, string sourceFile, bool isTopLevel, bool lineNumbersEnabled, bool generateProbes) + public ActorCompiler( + Actor actor, + string sourceFile, + bool isTopLevel, + bool lineNumbersEnabled, + bool generateProbes + ) { this.actor = actor; this.sourceFile = sourceFile; @@ -335,10 +463,12 @@ public ActorCompiler(Actor actor, string sourceFile, bool isTopLevel, bool lineN FindState(); } - private ulong ByteToLong(byte[] bytes) { + private ulong ByteToLong(byte[] bytes) + { // NOTE: Always assume big endian. ulong result = 0; - foreach(var b in bytes) { + foreach (var b in bytes) + { result += b; result <<= 8; } @@ -346,7 +476,8 @@ private ulong ByteToLong(byte[] bytes) { } // Generates the identifier for the ACTOR - private Tuple GetUidFromString(string str) { + private Tuple GetUidFromString(string str) + { byte[] sha256Hash = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(str)); byte[] first = sha256Hash.Take(8).ToArray(); byte[] second = sha256Hash.Skip(8).Take(8).ToArray(); @@ -354,19 +485,30 @@ private Tuple GetUidFromString(string str) { } // Writes the function that returns the Actor object - private void WriteActorFunction(TextWriter writer, string fullReturnType) { + private void WriteActorFunction(TextWriter writer, string fullReturnType) + { WriteTemplate(writer); LineNumber(writer, actor.SourceLine); - foreach (string attribute in actor.attributes) { + foreach (string attribute in actor.attributes) + { writer.Write(attribute + " "); } - if (actor.isStatic) writer.Write("static "); - writer.WriteLine("{0} {3}{1}( {2} ) {{", fullReturnType, actor.name, string.Join(", ", ParameterList()), actor.nameSpace==null ? "" : actor.nameSpace + "::"); + if (actor.isStatic) + writer.Write("static "); + writer.WriteLine( + "{0} {3}{1}( {2} ) {{", + fullReturnType, + actor.name, + string.Join(", ", ParameterList()), + actor.nameSpace == null ? "" : actor.nameSpace + "::" + ); LineNumber(writer, actor.SourceLine); - string newActor = string.Format("new {0}({1})", - fullClassName, - string.Join(", ", actor.parameters.Select(p => p.name).ToArray())); + string newActor = string.Format( + "new {0}({1})", + fullClassName, + string.Join(", ", actor.parameters.Select(p => p.name).ToArray()) + ); if (actor.returnType != null) writer.WriteLine("\treturn Future<{1}>({0});", newActor, actor.returnType); @@ -376,21 +518,27 @@ private void WriteActorFunction(TextWriter writer, string fullReturnType) { } // Writes the class of the Actor object - private void WriteActorClass(TextWriter writer, string fullStateClassName, Function body) { + private void WriteActorClass(TextWriter writer, string fullStateClassName, Function body) + { // The final actor class mixes in the State class, the Actor base class and all callback classes writer.WriteLine("// This generated class is to be used only via {0}()", actor.name); WriteTemplate(writer); LineNumber(writer, actor.SourceLine); - string callback_base_classes = string.Join(", ", callbacks.Select(c=>string.Format("public {0}", c.type))); - if (callback_base_classes != "") callback_base_classes += ", "; - writer.WriteLine("class {0} final : public Actor<{2}>, {3}public FastAllocated<{1}>, public {4} {{", + string callback_base_classes = string.Join( + ", ", + callbacks.Select(c => string.Format("public {0}", c.type)) + ); + if (callback_base_classes != "") + callback_base_classes += ", "; + writer.WriteLine( + "class {0} final : public Actor<{2}>, {3}public FastAllocated<{1}>, public {4} {{", className, fullClassName, actor.returnType == null ? "void" : actor.returnType, callback_base_classes, fullStateClassName - ); + ); writer.WriteLine("public:"); writer.WriteLine("\tusing FastAllocated<{0}>::operator new;", fullClassName); writer.WriteLine("\tusing FastAllocated<{0}>::operator delete;", fullClassName); @@ -399,23 +547,32 @@ private void WriteActorClass(TextWriter writer, string fullStateClassName, Funct var actorIdentifier = GetUidFromString(actorIdentifierKey); uidObjects.Add((actorIdentifier.Item1, actorIdentifier.Item2), actorIdentifierKey); // NOTE UL is required as a u64 postfix for large integers, otherwise Clang would complain - writer.WriteLine("\tstatic constexpr ActorIdentifier __actorIdentifier = UID({0}UL, {1}UL);", actorIdentifier.Item1, actorIdentifier.Item2); + writer.WriteLine( + "\tstatic constexpr ActorIdentifier __actorIdentifier = UID({0}UL, {1}UL);", + actorIdentifier.Item1, + actorIdentifier.Item2 + ); writer.WriteLine("\tActiveActorHelper activeActorHelper;"); writer.WriteLine("#pragma clang diagnostic push"); writer.WriteLine("#pragma clang diagnostic ignored \"-Wdelete-non-virtual-dtor\""); if (actor.returnType != null) - writer.WriteLine(@" void destroy() override {{ + writer.WriteLine( + @" void destroy() override {{ activeActorHelper.~ActiveActorHelper(); static_cast*>(this)->~Actor(); operator delete(this); - }}", actor.returnType); + }}", + actor.returnType + ); else - writer.WriteLine(@" void destroy() {{ + writer.WriteLine( + @" void destroy() {{ activeActorHelper.~ActiveActorHelper(); static_cast*>(this)->~Actor(); operator delete(this); - }}"); + }}" + ); writer.WriteLine("#pragma clang diagnostic pop"); foreach (var cb in callbacks) @@ -425,23 +582,25 @@ private void WriteActorClass(TextWriter writer, string fullStateClassName, Funct WriteConstructor(body, writer, fullStateClassName); WriteCancelFunc(writer); writer.WriteLine("};"); - } public void Write(TextWriter writer) { string fullReturnType = - actor.returnType != null ? string.Format("Future<{0}>", actor.returnType) - : "void"; + actor.returnType != null ? string.Format("Future<{0}>", actor.returnType) : "void"; for (int i = 0; ; i++) { - className = string.Format("{3}{0}{1}Actor{2}", + className = string.Format( + "{3}{0}{1}Actor{2}", actor.name.Substring(0, 1).ToUpper(), actor.name.Substring(1), i != 0 ? i.ToString() : "", - actor.enclosingClass != null && actor.isForwardDeclaration ? actor.enclosingClass.Replace("::", "_") + "_" - : actor.nameSpace != null ? actor.nameSpace.Replace("::", "_") + "_" - : ""); + actor.enclosingClass != null && actor.isForwardDeclaration + ? actor.enclosingClass.Replace("::", "_") + "_" + : actor.nameSpace != null + ? actor.nameSpace.Replace("::", "_") + "_" + : "" + ); if (actor.isForwardDeclaration || usedClassNames.Add(className)) break; } @@ -453,22 +612,35 @@ public void Write(TextWriter writer) // e.g. SimpleTimerActorState stateClassName = className + "State"; // e.g. SimpleTimerActorState - var fullStateClassName = stateClassName + GetTemplateActuals(new VarDeclaration { type = "class", name = fullClassName }); + var fullStateClassName = + stateClassName + + GetTemplateActuals(new VarDeclaration { type = "class", name = fullClassName }); - if (actor.isForwardDeclaration) { - foreach (string attribute in actor.attributes) { + if (actor.isForwardDeclaration) + { + foreach (string attribute in actor.attributes) + { writer.Write(attribute + " "); } - if (actor.isStatic) writer.Write("static "); - writer.WriteLine("{0} {3}{1}( {2} );", fullReturnType, actor.name, string.Join(", ", ParameterList()), actor.nameSpace==null ? "" : actor.nameSpace + "::"); - if (actor.enclosingClass != null) { + if (actor.isStatic) + writer.Write("static "); + writer.WriteLine( + "{0} {3}{1}( {2} );", + fullReturnType, + actor.name, + string.Join(", ", ParameterList()), + actor.nameSpace == null ? "" : actor.nameSpace + "::" + ); + if (actor.enclosingClass != null) + { writer.WriteLine("template friend class {0};", stateClassName); } return; } var body = getFunction("", "body", loopDepth0); - var bodyContext = new Context { + var bodyContext = new Context + { target = body, catchFErr = getFunction(body.name, "Catch", "Error error", loopDepth0), }; @@ -478,9 +650,16 @@ public void Write(TextWriter writer) if (endContext.target != null) { if (actor.returnType == null) - CompileStatement(new ReturnStatement { FirstSourceLine = actor.SourceLine, expression = "" }, endContext ); + CompileStatement( + new ReturnStatement { FirstSourceLine = actor.SourceLine, expression = "" }, + endContext + ); else - throw new Error(actor.SourceLine, "Actor {0} fails to return a value", actor.name); + throw new Error( + actor.SourceLine, + "Actor {0} fails to return a value", + actor.name + ); } if (actor.returnType != null) @@ -494,7 +673,8 @@ public void Write(TextWriter writer) } bodyContext.catchFErr.WriteLine("loopDepth = 0;"); - if (isTopLevel && actor.nameSpace == null) writer.WriteLine("namespace {"); + if (isTopLevel && actor.nameSpace == null) + writer.WriteLine("namespace {"); // The "State" class contains all state and user code, to make sure that state names are accessible to user code but // inherited members of Actor, Callback etc are not. @@ -516,7 +696,8 @@ public void Write(TextWriter writer) WriteActorClass(writer, fullStateClassName, body); - if (isTopLevel && actor.nameSpace == null) writer.WriteLine("} // namespace"); // namespace + if (isTopLevel && actor.nameSpace == null) + writer.WriteLine("} // namespace"); // namespace WriteActorFunction(writer, fullReturnType); @@ -530,159 +711,248 @@ public void Write(TextWriter writer) const string thisAddress = "reinterpret_cast(this)"; - void ProbeEnter(Function fun, string name, int index = -1) { - if (generateProbes) { - fun.WriteLine("fdb_probe_actor_enter(\"{0}\", {1}, {2});", name, thisAddress, index); + void ProbeEnter(Function fun, string name, int index = -1) + { + if (generateProbes) + { + fun.WriteLine( + "fdb_probe_actor_enter(\"{0}\", {1}, {2});", + name, + thisAddress, + index + ); } var blockIdentifier = GetUidFromString(fun.name); fun.WriteLine("#ifdef WITH_ACAC"); - fun.WriteLine("static constexpr ActorBlockIdentifier __identifier = UID({0}UL, {1}UL);", blockIdentifier.Item1, blockIdentifier.Item2); - fun.WriteLine("ActorExecutionContextHelper __helper(static_cast<{0}*>(this)->activeActorHelper.actorID, __identifier);", className); + fun.WriteLine( + "static constexpr ActorBlockIdentifier __identifier = UID({0}UL, {1}UL);", + blockIdentifier.Item1, + blockIdentifier.Item2 + ); + fun.WriteLine( + "ActorExecutionContextHelper __helper(static_cast<{0}*>(this)->activeActorHelper.actorID, __identifier);", + className + ); fun.WriteLine("#endif // WITH_ACAC"); } - void ProbeExit(Function fun, string name, int index = -1) { - if (generateProbes) { + void ProbeExit(Function fun, string name, int index = -1) + { + if (generateProbes) + { fun.WriteLine("fdb_probe_actor_exit(\"{0}\", {1}, {2});", name, thisAddress, index); } } - void ProbeCreate(Function fun, string name) { - if (generateProbes) { + void ProbeCreate(Function fun, string name) + { + if (generateProbes) + { fun.WriteLine("fdb_probe_actor_create(\"{0}\", {1});", name, thisAddress); } } - void ProbeDestroy(Function fun, string name) { - if (generateProbes) { + void ProbeDestroy(Function fun, string name) + { + if (generateProbes) + { fun.WriteLine("fdb_probe_actor_destroy(\"{0}\", {1});", name, thisAddress); } } void LineNumber(TextWriter writer, int SourceLine) { - if(SourceLine == 0) + if (SourceLine == 0) { throw new Exception("Internal error: Invalid source line (0)"); } if (LineNumbersEnabled) - writer.WriteLine("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t#line {0} \"{1}\"", SourceLine, sourceFile); + writer.WriteLine( + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t#line {0} \"{1}\"", + SourceLine, + sourceFile + ); } + void LineNumber(Function writer, int SourceLine) { - if(SourceLine == 0) + if (SourceLine == 0) { throw new Exception("Internal error: Invalid source line (0)"); } if (LineNumbersEnabled) - writer.WriteLineUnindented( string.Format("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t#line {0} \"{1}\"", SourceLine, sourceFile) ); + writer.WriteLineUnindented( + string.Format( + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t#line {0} \"{1}\"", + SourceLine, + sourceFile + ) + ); } - void TryCatch(Context cx, Function catchFErr, int catchLoopDepth, Action action, bool useLoopDepth = true) + void TryCatch( + Context cx, + Function catchFErr, + int catchLoopDepth, + Action action, + bool useLoopDepth = true + ) { - if (catchFErr!=null) + if (catchFErr != null) { cx.target.WriteLine("try {"); cx.target.Indent(+1); } action(); - if (catchFErr!=null) + if (catchFErr != null) { cx.target.Indent(-1); cx.target.WriteLine("}"); cx.target.WriteLine("catch (Error& error) {"); if (useLoopDepth) - cx.target.WriteLine("\tloopDepth = {0};", catchFErr.call("error", AdjustLoopDepth(catchLoopDepth))); + cx.target.WriteLine( + "\tloopDepth = {0};", + catchFErr.call("error", AdjustLoopDepth(catchLoopDepth)) + ); else cx.target.WriteLine("\t{0};", catchFErr.call("error", "0")); cx.target.WriteLine("} catch (...) {"); if (useLoopDepth) - cx.target.WriteLine("\tloopDepth = {0};", catchFErr.call("unknown_error()", AdjustLoopDepth(catchLoopDepth))); + cx.target.WriteLine( + "\tloopDepth = {0};", + catchFErr.call("unknown_error()", AdjustLoopDepth(catchLoopDepth)) + ); else cx.target.WriteLine("\t{0};", catchFErr.call("unknown_error()", "0")); cx.target.WriteLine("}"); } } + Context TryCatchCompile(CodeBlock block, Context cx) { - TryCatch(cx, cx.catchFErr, cx.tryLoopDepth, () => { - cx = Compile(block, cx, true); - if (cx.target != null) + TryCatch( + cx, + cx.catchFErr, + cx.tryLoopDepth, + () => { - var next = getFunction(cx.target.name, "cont", loopDepth); - cx.target.WriteLine("loopDepth = {0};", next.call("loopDepth")); - cx.target = next; - cx.next = null; + cx = Compile(block, cx, true); + if (cx.target != null) + { + var next = getFunction(cx.target.name, "cont", loopDepth); + cx.target.WriteLine("loopDepth = {0};", next.call("loopDepth")); + cx.target = next; + cx.next = null; + } } - }); + ); return cx; } void WriteTemplate(TextWriter writer, params VarDeclaration[] extraParameters) { - var formals = (actor.templateFormals!=null ? actor.templateFormals.AsEnumerable() : Enumerable.Empty()) + var formals = ( + actor.templateFormals != null + ? actor.templateFormals.AsEnumerable() + : Enumerable.Empty() + ) .Concat(extraParameters) .ToArray(); - if (formals.Length==0) return; + if (formals.Length == 0) + return; LineNumber(writer, actor.SourceLine); - writer.WriteLine("template <{0}>", - string.Join(", ", formals.Select( - p => string.Format("{0} {1}", p.type, p.name) - ).ToArray())); + writer.WriteLine( + "template <{0}>", + string.Join( + ", ", + formals.Select(p => string.Format("{0} {1}", p.type, p.name)).ToArray() + ) + ); } + string GetTemplateActuals(params VarDeclaration[] extraParameters) { - var formals = (actor.templateFormals != null ? actor.templateFormals.AsEnumerable() : Enumerable.Empty()) + var formals = ( + actor.templateFormals != null + ? actor.templateFormals.AsEnumerable() + : Enumerable.Empty() + ) .Concat(extraParameters) .ToArray(); - if (formals.Length == 0) return ""; - else return "<" + - string.Join(", ", formals.Select( - p => p.name - ).ToArray()) - + ">"; + if (formals.Length == 0) + return ""; + else + return "<" + string.Join(", ", formals.Select(p => p.name).ToArray()) + ">"; } bool WillContinue(Statement stmt) { - return Flatten(stmt).Any( - st => (st is ChooseStatement || st is WaitStatement || st is TryStatement)); + return Flatten(stmt) + .Any(st => (st is ChooseStatement || st is WaitStatement || st is TryStatement)); } CodeBlock AsCodeBlock(Statement statement) { // SOMEDAY: Is this necessary? Maybe we should just be compiling statements? var cb = statement as CodeBlock; - if (cb != null) return cb; + if (cb != null) + return cb; return new CodeBlock { statements = new Statement[] { statement } }; } void CompileStatement(PlainOldCodeStatement stmt, Context cx) { - LineNumber(cx.target,stmt.FirstSourceLine); + LineNumber(cx.target, stmt.FirstSourceLine); cx.target.WriteLine(stmt.code); } + void CompileStatement(StateDeclarationStatement stmt, Context cx) { // if this state declaration is at the very top of the actor body - if (actor.body.statements.Select(x => x as StateDeclarationStatement).TakeWhile(x => x != null).Any(x => x == stmt)) + if ( + actor + .body.statements.Select(x => x as StateDeclarationStatement) + .TakeWhile(x => x != null) + .Any(x => x == stmt) + ) { // Initialize the state in the constructor, not here - state.Add(new StateVar { SourceLine = stmt.FirstSourceLine, name = stmt.decl.name, type = stmt.decl.type, - initializer = stmt.decl.initializer, initializerConstructorSyntax = stmt.decl.initializerConstructorSyntax } ); + state.Add( + new StateVar + { + SourceLine = stmt.FirstSourceLine, + name = stmt.decl.name, + type = stmt.decl.type, + initializer = stmt.decl.initializer, + initializerConstructorSyntax = stmt.decl.initializerConstructorSyntax + } + ); } else { // State variables declared elsewhere must have a default constructor - state.Add(new StateVar { SourceLine = stmt.FirstSourceLine, name = stmt.decl.name, type = stmt.decl.type, initializer = null }); + state.Add( + new StateVar + { + SourceLine = stmt.FirstSourceLine, + name = stmt.decl.name, + type = stmt.decl.type, + initializer = null + } + ); if (stmt.decl.initializer != null) { LineNumber(cx.target, stmt.FirstSourceLine); - if (stmt.decl.initializerConstructorSyntax || stmt.decl.initializer=="") - cx.target.WriteLine("{0} = {1}({2});", stmt.decl.name, stmt.decl.type, stmt.decl.initializer); + if (stmt.decl.initializerConstructorSyntax || stmt.decl.initializer == "") + cx.target.WriteLine( + "{0} = {1}({2});", + stmt.decl.name, + stmt.decl.type, + stmt.decl.initializer + ); else cx.target.WriteLine("{0} = {1};", stmt.decl.name, stmt.decl.initializer); } @@ -693,62 +963,124 @@ void CompileStatement(ForStatement stmt, Context cx) { // for( initExpression; condExpression; nextExpression ) body; - bool noCondition = stmt.condExpression == "" || stmt.condExpression == "true" || stmt.condExpression == "1"; + bool noCondition = + stmt.condExpression == "" + || stmt.condExpression == "true" + || stmt.condExpression == "1"; if (!WillContinue(stmt.body)) { // We can write this loop without anything fancy, because there are no wait statements in it - if (EmitNativeLoop(stmt.FirstSourceLine, "for(" + stmt.initExpression + ";" + stmt.condExpression + ";" + stmt.nextExpression+")", stmt.body, cx) - && noCondition ) + if ( + EmitNativeLoop( + stmt.FirstSourceLine, + "for(" + + stmt.initExpression + + ";" + + stmt.condExpression + + ";" + + stmt.nextExpression + + ")", + stmt.body, + cx + ) && noCondition + ) cx.unreachable(); } else { // First compile the initExpression - CompileStatement( new PlainOldCodeStatement {code = stmt.initExpression + ";", FirstSourceLine = stmt.FirstSourceLine}, cx ); - + CompileStatement( + new PlainOldCodeStatement + { + code = stmt.initExpression + ";", + FirstSourceLine = stmt.FirstSourceLine + }, + cx + ); + // fullBody = { if (!(condExpression)) break; body; } - Statement fullBody = noCondition ? stmt.body : - new CodeBlock + Statement fullBody = noCondition + ? stmt.body + : new CodeBlock + { + statements = new Statement[] { - statements = new Statement[] { - new IfStatement + new IfStatement + { + expression = "!(" + stmt.condExpression + ")", + ifBody = new BreakStatement { - expression = "!(" + stmt.condExpression + ")", - ifBody = new BreakStatement { FirstSourceLine = stmt.FirstSourceLine }, FirstSourceLine = stmt.FirstSourceLine - } - }.Concat(AsCodeBlock(stmt.body).statements).ToArray(), - FirstSourceLine = stmt.FirstSourceLine - }; + }, + FirstSourceLine = stmt.FirstSourceLine + } + } + .Concat(AsCodeBlock(stmt.body).statements) + .ToArray(), + FirstSourceLine = stmt.FirstSourceLine + }; Function loopF = getFunction(cx.target.name, "loopHead", loopDepth); Function loopBody = getFunction(cx.target.name, "loopBody", loopDepth); Function breakF = getFunction(cx.target.name, "break", loopDepth); - Function continueF = stmt.nextExpression == "" ? loopF : getFunction(cx.target.name, "continue", loopDepth); + Function continueF = + stmt.nextExpression == "" + ? loopF + : getFunction(cx.target.name, "continue", loopDepth); // TODO: Could we use EmitNativeLoop() here? loopF.WriteLine("int oldLoopDepth = ++loopDepth;"); - loopF.WriteLine("while (loopDepth == oldLoopDepth) loopDepth = {0};", loopBody.call("loopDepth")); + loopF.WriteLine( + "while (loopDepth == oldLoopDepth) loopDepth = {0};", + loopBody.call("loopDepth") + ); - Function endLoop = Compile(AsCodeBlock(fullBody), cx.LoopContext( loopBody, breakF, continueF, +1 ), true).target; - if (endLoop != null && endLoop != loopBody) { + Function endLoop = Compile( + AsCodeBlock(fullBody), + cx.LoopContext(loopBody, breakF, continueF, +1), + true + ).target; + if (endLoop != null && endLoop != loopBody) + { if (stmt.nextExpression != "") - CompileStatement( new PlainOldCodeStatement {code = stmt.nextExpression + ";", FirstSourceLine = stmt.FirstSourceLine}, cx.WithTarget(endLoop) ); + CompileStatement( + new PlainOldCodeStatement + { + code = stmt.nextExpression + ";", + FirstSourceLine = stmt.FirstSourceLine + }, + cx.WithTarget(endLoop) + ); endLoop.WriteLine("if (loopDepth == 0) return {0};", loopF.call("0")); } cx.target.WriteLine("loopDepth = {0};", loopF.call("loopDepth")); - if (continueF != loopF && continueF.wasCalled) { - CompileStatement( new PlainOldCodeStatement {code = stmt.nextExpression + ";", FirstSourceLine = stmt.FirstSourceLine}, cx.WithTarget(continueF) ); + if (continueF != loopF && continueF.wasCalled) + { + CompileStatement( + new PlainOldCodeStatement + { + code = stmt.nextExpression + ";", + FirstSourceLine = stmt.FirstSourceLine + }, + cx.WithTarget(continueF) + ); continueF.WriteLine("if (loopDepth == 0) return {0};", loopF.call("0")); } if (breakF.wasCalled) - TryCatch(cx.WithTarget(breakF), cx.catchFErr, cx.tryLoopDepth, - () => { breakF.WriteLine("return {0};", cx.next.call("loopDepth")); }); + TryCatch( + cx.WithTarget(breakF), + cx.catchFErr, + cx.tryLoopDepth, + () => + { + breakF.WriteLine("return {0};", cx.next.call("loopDepth")); + } + ); else cx.unreachable(); } @@ -776,20 +1108,37 @@ void CompileStatement(RangeForStatement stmt, Context cx) StateVar container = state.FirstOrDefault(s => s.name == stmt.rangeExpression); if (container == null) { - throw new Error(stmt.FirstSourceLine, "container of range-based for with continuation must be a state variable"); + throw new Error( + stmt.FirstSourceLine, + "container of range-based for with continuation must be a state variable" + ); } var iter = getIteratorName(cx); - state.Add(new StateVar { SourceLine = stmt.FirstSourceLine, name = iter, type = "decltype(std::begin(std::declval<" + container.type + ">()))", initializer = null }); - var equivalent = new ForStatement { + state.Add( + new StateVar + { + SourceLine = stmt.FirstSourceLine, + name = iter, + type = "decltype(std::begin(std::declval<" + container.type + ">()))", + initializer = null + } + ); + var equivalent = new ForStatement + { initExpression = iter + " = std::begin(" + stmt.rangeExpression + ")", condExpression = iter + " != std::end(" + stmt.rangeExpression + ")", nextExpression = "++" + iter, FirstSourceLine = stmt.FirstSourceLine, body = new CodeBlock { - statements = new Statement[] { - new PlainOldCodeStatement { FirstSourceLine = stmt.FirstSourceLine, code = stmt.rangeDecl + " = *" + iter + ";" }, + statements = new Statement[] + { + new PlainOldCodeStatement + { + FirstSourceLine = stmt.FirstSourceLine, + code = stmt.rangeDecl + " = *" + iter + ";" + }, stmt.body } } @@ -798,7 +1147,12 @@ void CompileStatement(RangeForStatement stmt, Context cx) } else { - EmitNativeLoop(stmt.FirstSourceLine, "for( " + stmt.rangeDecl + " : " + stmt.rangeExpression + " )", stmt.body, cx); + EmitNativeLoop( + stmt.FirstSourceLine, + "for( " + stmt.rangeDecl + " : " + stmt.rangeExpression + " )", + stmt.body, + cx + ); } } @@ -833,8 +1187,12 @@ private bool EmitNativeLoop(int sourceLine, string head, Statement body, Context LineNumber(cx.target, sourceLine); cx.target.WriteLine(head + " {"); cx.target.Indent(+1); - var literalBreak = new LiteralBreak(); - Compile(AsCodeBlock(body), cx.LoopContext(cx.target, literalBreak, new LiteralContinue(), 0), true); + var literalBreak = new LiteralBreak(); + Compile( + AsCodeBlock(body), + cx.LoopContext(cx.target, literalBreak, new LiteralContinue(), 0), + true + ); cx.target.Indent(-1); cx.target.WriteLine("}"); return !literalBreak.wasCalled; @@ -847,56 +1205,105 @@ void CompileStatement(ChooseStatement stmt, Context cx) var codeblock = stmt.body as CodeBlock; if (codeblock == null) - throw new Error(stmt.FirstSourceLine, "'choose' must be followed by a compound statement."); - var choices = codeblock.statements - .OfType() - .Select( (ch,i) => new { - Stmt = ch, - Group = group, - Index = this.whenCount+i, - Body = getFunction(cx.target.name, "when", - new string[] { string.Format("{0} const& {2}{1}", ch.wait.result.type, ch.wait.result.name, ch.wait.resultIsState?"__":""), loopDepth }, - new string[] { string.Format("{0} && {2}{1}", ch.wait.result.type, ch.wait.result.name, ch.wait.resultIsState?"__":""), loopDepth } - ), - Future = string.Format("__when_expr_{0}", this.whenCount + i), - CallbackType = string.Format("{3}< {0}, {1}, {2} >", fullClassName, this.whenCount + i, ch.wait.result.type, ch.wait.isWaitNext ? "ActorSingleCallback" : "ActorCallback"), - CallbackTypeInStateClass = string.Format("{3}< {0}, {1}, {2} >", className, this.whenCount + i, ch.wait.result.type, ch.wait.isWaitNext ? "ActorSingleCallback" : "ActorCallback") - }) + throw new Error( + stmt.FirstSourceLine, + "'choose' must be followed by a compound statement." + ); + var choices = codeblock + .statements.OfType() + .Select( + (ch, i) => + new + { + Stmt = ch, + Group = group, + Index = this.whenCount + i, + Body = getFunction( + cx.target.name, + "when", + new string[] + { + string.Format( + "{0} const& {2}{1}", + ch.wait.result.type, + ch.wait.result.name, + ch.wait.resultIsState ? "__" : "" + ), + loopDepth + }, + new string[] + { + string.Format( + "{0} && {2}{1}", + ch.wait.result.type, + ch.wait.result.name, + ch.wait.resultIsState ? "__" : "" + ), + loopDepth + } + ), + Future = string.Format("__when_expr_{0}", this.whenCount + i), + CallbackType = string.Format( + "{3}< {0}, {1}, {2} >", + fullClassName, + this.whenCount + i, + ch.wait.result.type, + ch.wait.isWaitNext ? "ActorSingleCallback" : "ActorCallback" + ), + CallbackTypeInStateClass = string.Format( + "{3}< {0}, {1}, {2} >", + className, + this.whenCount + i, + ch.wait.result.type, + ch.wait.isWaitNext ? "ActorSingleCallback" : "ActorCallback" + ) + } + ) .ToArray(); this.whenCount += choices.Length; if (choices.Length != codeblock.statements.Length) - throw new Error(codeblock.statements.First(x=>!(x is WhenStatement)).FirstSourceLine, "only 'when' statements are valid in an 'choose' block."); + throw new Error( + codeblock.statements.First(x => !(x is WhenStatement)).FirstSourceLine, + "only 'when' statements are valid in an 'choose' block." + ); var exitFunc = getFunction("exitChoose", ""); exitFunc.returnType = "void"; exitFunc.WriteLine("if ({0}->actor_wait_state > 0) {0}->actor_wait_state = 0;", This); - foreach(var ch in choices) + foreach (var ch in choices) exitFunc.WriteLine("{0}->{1}::remove();", This, ch.CallbackTypeInStateClass); exitFunc.endIsUnreachable = true; //state.Add(new StateVar { SourceLine = stmt.FirstSourceLine, type = "CallbackGroup", name = cbGroup, callbackCatchFErr = cx.catchFErr }); bool reachable = false; - foreach(var ch in choices) { - callbacks.Add(new CallbackVar - { - SourceLine = ch.Stmt.FirstSourceLine, - CallbackGroup = ch.Group, - type = ch.CallbackType - }); + foreach (var ch in choices) + { + callbacks.Add( + new CallbackVar + { + SourceLine = ch.Stmt.FirstSourceLine, + CallbackGroup = ch.Group, + type = ch.CallbackType + } + ); var r = ch.Body; if (ch.Stmt.wait.resultIsState) { Function overload = r.popOverload(); - CompileStatement(new StateDeclarationStatement - { - FirstSourceLine = ch.Stmt.FirstSourceLine, - decl = new VarDeclaration { - type = ch.Stmt.wait.result.type, - name = ch.Stmt.wait.result.name, - initializer = "__" + ch.Stmt.wait.result.name, - initializerConstructorSyntax = false - } - }, cx.WithTarget(r)); + CompileStatement( + new StateDeclarationStatement + { + FirstSourceLine = ch.Stmt.FirstSourceLine, + decl = new VarDeclaration + { + type = ch.Stmt.wait.result.type, + name = ch.Stmt.wait.result.name, + initializer = "__" + ch.Stmt.wait.result.name, + initializerConstructorSyntax = false + } + }, + cx.WithTarget(r) + ); if (overload != null) { overload.WriteLine("{0} = std::move(__{0});", ch.Stmt.wait.result.name); @@ -912,39 +1319,70 @@ void CompileStatement(ChooseStatement stmt, Context cx) reachable = true; if (cx.next.formalParameters.Length == 1) r.WriteLine("loopDepth = {0};", cx.next.call("loopDepth")); - else { + else + { Function overload = r.popOverload(); - r.WriteLine("loopDepth = {0};", cx.next.call(ch.Stmt.wait.result.name, "loopDepth")); - if (overload != null) { - overload.WriteLine("loopDepth = {0};", cx.next.call(string.Format("std::move({0})", ch.Stmt.wait.result.name), "loopDepth")); + r.WriteLine( + "loopDepth = {0};", + cx.next.call(ch.Stmt.wait.result.name, "loopDepth") + ); + if (overload != null) + { + overload.WriteLine( + "loopDepth = {0};", + cx.next.call( + string.Format("std::move({0})", ch.Stmt.wait.result.name), + "loopDepth" + ) + ); r.setOverload(overload); } } } - var cbFunc = new Function { + var cbFunc = new Function + { name = "callback_fire", returnType = "void", - formalParameters = new string[] { + formalParameters = new string[] + { ch.CallbackTypeInStateClass + "*", ch.Stmt.wait.result.type + " const& value" }, endIsUnreachable = true }; - cbFunc.addOverload(ch.CallbackTypeInStateClass + "*", ch.Stmt.wait.result.type + " && value"); + cbFunc.addOverload( + ch.CallbackTypeInStateClass + "*", + ch.Stmt.wait.result.type + " && value" + ); functions.Add(string.Format("{0}#{1}", cbFunc.name, ch.Index), cbFunc); cbFunc.Indent(codeIndent); ProbeEnter(cbFunc, actor.name, ch.Index); cbFunc.WriteLine("{0};", exitFunc.call()); Function _overload = cbFunc.popOverload(); - TryCatch(cx.WithTarget(cbFunc), cx.catchFErr, cx.tryLoopDepth, () => { - cbFunc.WriteLine("{0};", ch.Body.call("value", "0")); - }, false); - if (_overload != null) { - TryCatch(cx.WithTarget(_overload), cx.catchFErr, cx.tryLoopDepth, () => { - _overload.WriteLine("{0};", ch.Body.call("std::move(value)", "0")); - }, false); + TryCatch( + cx.WithTarget(cbFunc), + cx.catchFErr, + cx.tryLoopDepth, + () => + { + cbFunc.WriteLine("{0};", ch.Body.call("value", "0")); + }, + false + ); + if (_overload != null) + { + TryCatch( + cx.WithTarget(_overload), + cx.catchFErr, + cx.tryLoopDepth, + () => + { + _overload.WriteLine("{0};", ch.Body.call("std::move(value)", "0")); + }, + false + ); cbFunc.setOverload(_overload); } ProbeExit(cbFunc, actor.name, ch.Index); @@ -953,7 +1391,8 @@ void CompileStatement(ChooseStatement stmt, Context cx) { name = "callback_error", returnType = "void", - formalParameters = new string[] { + formalParameters = new string[] + { ch.CallbackTypeInStateClass + "*", "Error err" }, @@ -963,10 +1402,16 @@ void CompileStatement(ChooseStatement stmt, Context cx) errFunc.Indent(codeIndent); ProbeEnter(errFunc, actor.name, ch.Index); errFunc.WriteLine("{0};", exitFunc.call()); - TryCatch(cx.WithTarget(errFunc), cx.catchFErr, cx.tryLoopDepth, () => - { - errFunc.WriteLine("{0};", cx.catchFErr.call("err", "0")); - }, false); + TryCatch( + cx.WithTarget(errFunc), + cx.catchFErr, + cx.tryLoopDepth, + () => + { + errFunc.WriteLine("{0};", cx.catchFErr.call("err", "0")); + }, + false + ); ProbeExit(errFunc, actor.name, ch.Index); } @@ -975,7 +1420,13 @@ void CompileStatement(ChooseStatement stmt, Context cx) { string getFunc = ch.Stmt.wait.isWaitNext ? "pop" : "get"; LineNumber(cx.target, ch.Stmt.wait.FirstSourceLine); - cx.target.WriteLine("{2}<{3}> {0} = {1};", ch.Future, ch.Stmt.wait.futureExpression, ch.Stmt.wait.isWaitNext ? "FutureStream" : "StrictFuture", ch.Stmt.wait.result.type); + cx.target.WriteLine( + "{2}<{3}> {0} = {1};", + ch.Future, + ch.Stmt.wait.futureExpression, + ch.Stmt.wait.isWaitNext ? "FutureStream" : "StrictFuture", + ch.Stmt.wait.result.type + ); if (firstChoice) { @@ -985,21 +1436,40 @@ void CompileStatement(ChooseStatement stmt, Context cx) firstChoice = false; LineNumber(cx.target, stmt.FirstSourceLine); if (actor.IsCancellable()) - cx.target.WriteLine("if ({1}->actor_wait_state < 0) return {0};", cx.catchFErr.call("actor_cancelled()", AdjustLoopDepth(cx.tryLoopDepth)), This); + cx.target.WriteLine( + "if ({1}->actor_wait_state < 0) return {0};", + cx.catchFErr.call( + "actor_cancelled()", + AdjustLoopDepth(cx.tryLoopDepth) + ), + This + ); } - cx.target.WriteLine("if ({0}.isReady()) {{ if ({0}.isError()) return {2}; else return {1}; }};", ch.Future, ch.Body.call(ch.Future + "." + getFunc + "()", "loopDepth"), cx.catchFErr.call(ch.Future + ".getError()", AdjustLoopDepth(cx.tryLoopDepth))); + cx.target.WriteLine( + "if ({0}.isReady()) {{ if ({0}.isError()) return {2}; else return {1}; }};", + ch.Future, + ch.Body.call(ch.Future + "." + getFunc + "()", "loopDepth"), + cx.catchFErr.call(ch.Future + ".getError()", AdjustLoopDepth(cx.tryLoopDepth)) + ); } cx.target.WriteLine("{1}->actor_wait_state = {0};", group, This); foreach (var ch in choices) { LineNumber(cx.target, ch.Stmt.wait.FirstSourceLine); - cx.target.WriteLine("{0}.addCallbackAndClear(static_cast<{1}*>({2}));", ch.Future, ch.CallbackTypeInStateClass, This); + cx.target.WriteLine( + "{0}.addCallbackAndClear(static_cast<{1}*>({2}));", + ch.Future, + ch.CallbackTypeInStateClass, + This + ); } - cx.target.WriteLine("loopDepth = 0;");//cx.target.WriteLine("return 0;"); + cx.target.WriteLine("loopDepth = 0;"); //cx.target.WriteLine("return 0;"); - if (!reachable) cx.unreachable(); + if (!reachable) + cx.unreachable(); } + void CompileStatement(BreakStatement stmt, Context cx) { if (cx.breakF == null) @@ -1008,10 +1478,14 @@ void CompileStatement(BreakStatement stmt, Context cx) cx.target.WriteLine("{0};", cx.breakF.call()); else { - cx.target.WriteLine("return {0}; // break", cx.breakF.call("loopDepth==0?0:loopDepth-1")); + cx.target.WriteLine( + "return {0}; // break", + cx.breakF.call("loopDepth==0?0:loopDepth-1") + ); } cx.unreachable(); } + void CompileStatement(ContinueStatement stmt, Context cx) { if (cx.continueF == null) @@ -1022,14 +1496,17 @@ void CompileStatement(ContinueStatement stmt, Context cx) cx.target.WriteLine("return {0}; // continue", cx.continueF.call("loopDepth")); cx.unreachable(); } + void CompileStatement(WaitStatement stmt, Context cx) { var equiv = new ChooseStatement { body = new CodeBlock { - statements = new Statement[] { - new WhenStatement { + statements = new Statement[] + { + new WhenStatement + { wait = stmt, body = null, FirstSourceLine = stmt.FirstSourceLine, @@ -1039,16 +1516,21 @@ void CompileStatement(WaitStatement stmt, Context cx) }, FirstSourceLine = stmt.FirstSourceLine }; - if (!stmt.resultIsState) { - cx.next.formalParameters = new string[] { - string.Format("{0} const& {1}", stmt.result.type, stmt.result.name), - loopDepth }; + if (!stmt.resultIsState) + { + cx.next.formalParameters = new string[] + { + string.Format("{0} const& {1}", stmt.result.type, stmt.result.name), + loopDepth + }; cx.next.addOverload( string.Format("{0} && {1}", stmt.result.type, stmt.result.name), - loopDepth); + loopDepth + ); } CompileStatement(equiv, cx); } + void CompileStatement(CodeBlock stmt, Context cx) { cx.target.WriteLine("{"); @@ -1061,11 +1543,15 @@ void CompileStatement(CodeBlock stmt, Context cx) else if (end.target != cx.target) end.target.WriteLine("loopDepth = {0};", cx.next.call("loopDepth")); } + void CompileStatement(ReturnStatement stmt, Context cx) { LineNumber(cx.target, stmt.FirstSourceLine); if ((stmt.expression == "") != (actor.returnType == null)) - throw new Error(stmt.FirstSourceLine, "Return statement does not match actor declaration"); + throw new Error( + stmt.FirstSourceLine, + "Return statement does not match actor declaration" + ); if (actor.returnType != null) { if (stmt.expression == "Never()") @@ -1078,27 +1564,45 @@ void CompileStatement(ReturnStatement stmt, Context cx) { // Short circuit if there are no futures outstanding, but still evaluate the expression // if it has side effects - cx.target.WriteLine("if (!{0}->SAV<{1}>::futures) {{ (void)({2}); this->~{3}(); {0}->destroy(); return 0; }}", This, actor.returnType, stmt.expression, stateClassName); + cx.target.WriteLine( + "if (!{0}->SAV<{1}>::futures) {{ (void)({2}); this->~{3}(); {0}->destroy(); return 0; }}", + This, + actor.returnType, + stmt.expression, + stateClassName + ); // Build the return value directly in SAV::value_storage // If the expression is exactly the name of a state variable, std::move() it if (state.Exists(s => s.name == stmt.expression)) { - cx.target.WriteLine("new (&{0}->SAV< {1} >::value()) {1}(std::move({2})); // state_var_RVO", This, actor.returnType, stmt.expression); + cx.target.WriteLine( + "new (&{0}->SAV< {1} >::value()) {1}(std::move({2})); // state_var_RVO", + This, + actor.returnType, + stmt.expression + ); } else { - cx.target.WriteLine("new (&{0}->SAV< {1} >::value()) {1}({2});", This, actor.returnType, stmt.expression); + cx.target.WriteLine( + "new (&{0}->SAV< {1} >::value()) {1}({2});", + This, + actor.returnType, + stmt.expression + ); } // Destruct state cx.target.WriteLine("this->~{0}();", stateClassName); // Tell SAV to return the value we already constructed in value_storage cx.target.WriteLine("{0}->finishSendAndDelPromiseRef();", This); } - } else + } + else cx.target.WriteLine("delete {0};", This); cx.target.WriteLine("return 0;"); cx.unreachable(); } + void CompileStatement(IfStatement stmt, Context cx) { bool useContinuation = WillContinue(stmt.ifBody) || WillContinue(stmt.elseBody); @@ -1130,66 +1634,118 @@ void CompileStatement(IfStatement stmt, Context cx) } if (ifTarget == null && stmt.elseBody != null && elseTarget == null) cx.unreachable(); - else if (!cx.next.wasCalled && useContinuation) + else if (!cx.next.wasCalled && useContinuation) throw new Exception("Internal error: IfStatement: next not called?"); } + void CompileStatement(TryStatement stmt, Context cx) { bool reachable = false; - if (stmt.catches.Count != 1) throw new Error(stmt.FirstSourceLine, "try statement must have exactly one catch clause"); + if (stmt.catches.Count != 1) + throw new Error( + stmt.FirstSourceLine, + "try statement must have exactly one catch clause" + ); var c = stmt.catches[0]; string catchErrorParameterName = ""; - if (c.expression != "...") { - string exp = c.expression.Replace(" ",""); + if (c.expression != "...") + { + string exp = c.expression.Replace(" ", ""); if (!exp.StartsWith("Error&")) - throw new Error(c.FirstSourceLine, "Only type 'Error' or '...' may be caught in an actor function"); + throw new Error( + c.FirstSourceLine, + "Only type 'Error' or '...' may be caught in an actor function" + ); catchErrorParameterName = exp.Substring(6); } - if (catchErrorParameterName == "") catchErrorParameterName = "__current_error"; - - var catchFErr = getFunction(cx.target.name, "Catch", "const Error& " + catchErrorParameterName, loopDepth0); + if (catchErrorParameterName == "") + catchErrorParameterName = "__current_error"; + + var catchFErr = getFunction( + cx.target.name, + "Catch", + "const Error& " + catchErrorParameterName, + loopDepth0 + ); catchFErr.exceptionParameterIs = catchErrorParameterName; var end = TryCatchCompile(AsCodeBlock(stmt.tryBody), cx.WithCatch(catchFErr)); - if (end.target != null) reachable = true; - - if (end.target!=null) - TryCatch(end, cx.catchFErr, cx.tryLoopDepth, () => - end.target.WriteLine("loopDepth = {0};", cx.next.call("loopDepth"))); + if (end.target != null) + reachable = true; + + if (end.target != null) + TryCatch( + end, + cx.catchFErr, + cx.tryLoopDepth, + () => end.target.WriteLine("loopDepth = {0};", cx.next.call("loopDepth")) + ); // Now to write the catch function - TryCatch(cx.WithTarget(catchFErr), cx.catchFErr, cx.tryLoopDepth, () => + TryCatch( + cx.WithTarget(catchFErr), + cx.catchFErr, + cx.tryLoopDepth, + () => { var cend = Compile(AsCodeBlock(c.body), cx.WithTarget(catchFErr), true); - if (cend.target != null) cend.target.WriteLine("loopDepth = {0};", cx.next.call("loopDepth")); - if (cend.target != null) reachable = true; - }); + if (cend.target != null) + cend.target.WriteLine("loopDepth = {0};", cx.next.call("loopDepth")); + if (cend.target != null) + reachable = true; + } + ); - if (!reachable) cx.unreachable(); + if (!reachable) + cx.unreachable(); } + void CompileStatement(ThrowStatement stmt, Context cx) { LineNumber(cx.target, stmt.FirstSourceLine); - + if (stmt.expression == "") { if (cx.target.exceptionParameterIs != null) - cx.target.WriteLine("return {0};", cx.catchFErr.call(cx.target.exceptionParameterIs, AdjustLoopDepth( cx.tryLoopDepth ))); + cx.target.WriteLine( + "return {0};", + cx.catchFErr.call( + cx.target.exceptionParameterIs, + AdjustLoopDepth(cx.tryLoopDepth) + ) + ); else - throw new Error(stmt.FirstSourceLine, "throw statement with no expression has no current exception in scope"); + throw new Error( + stmt.FirstSourceLine, + "throw statement with no expression has no current exception in scope" + ); } else - cx.target.WriteLine("return {0};", cx.catchFErr.call(stmt.expression, AdjustLoopDepth( cx.tryLoopDepth ))); + cx.target.WriteLine( + "return {0};", + cx.catchFErr.call(stmt.expression, AdjustLoopDepth(cx.tryLoopDepth)) + ); cx.unreachable(); } + void CompileStatement(Statement stmt, Context cx) { // Use reflection for double dispatch. SOMEDAY: Use a Dictionary> and expression trees to memoize - var method = typeof(ActorCompiler).GetMethod("CompileStatement", - System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.ExactBinding, - null, new Type[] { stmt.GetType(), typeof(Context) }, null); + var method = typeof(ActorCompiler).GetMethod( + "CompileStatement", + System.Reflection.BindingFlags.NonPublic + | System.Reflection.BindingFlags.Instance + | System.Reflection.BindingFlags.ExactBinding, + null, + new Type[] { stmt.GetType(), typeof(Context) }, + null + ); if (method == null) - throw new Error(stmt.FirstSourceLine, "Statement type {0} not supported yet.", stmt.GetType().Name); + throw new Error( + stmt.FirstSourceLine, + "Statement type {0} not supported yet.", + stmt.GetType().Name + ); try { method.Invoke(this, new object[] { stmt, cx }); @@ -1197,8 +1753,13 @@ void CompileStatement(Statement stmt, Context cx) catch (System.Reflection.TargetInvocationException e) { if (!(e.InnerException is Error)) - Console.Error.WriteLine("\tHit error <{0}> for statement type {1} at line {2}\n\tStack Trace:\n{3}", - e.InnerException.Message, stmt.GetType().Name, stmt.FirstSourceLine, e.InnerException.StackTrace); + Console.Error.WriteLine( + "\tHit error <{0}> for statement type {1} at line {2}\n\tStack Trace:\n{3}", + e.InnerException.Message, + stmt.GetType().Name, + stmt.FirstSourceLine, + e.InnerException.StackTrace + ); throw e.InnerException; } } @@ -1207,9 +1768,10 @@ void CompileStatement(Statement stmt, Context cx) // does not modify its parameter // The target of the returned context is null if the end of the CodeBlock is unreachable (otherwise // it is the target Function to which the end of the CodeBlock was written) - Context Compile(CodeBlock block, Context context, bool okToContinue=true) + Context Compile(CodeBlock block, Context context, bool okToContinue = true) { - var cx = context.Clone(); cx.next = null; + var cx = context.Clone(); + cx.next = null; foreach (var stmt in block.statements) { if (cx.target == null) @@ -1223,8 +1785,10 @@ Context Compile(CodeBlock block, Context context, bool okToContinue=true) CompileStatement(stmt, cx); if (cx.next.wasCalled) { - if (cx.target == null) throw new Exception("Unreachable continuation called?"); - if (!okToContinue) throw new Exception("Unexpected continuation"); + if (cx.target == null) + throw new Exception("Unreachable continuation called?"); + if (!okToContinue) + throw new Exception("Unexpected continuation"); cx.target = cx.next; cx.next = null; } @@ -1256,11 +1820,13 @@ void WriteFunctions(TextWriter writer) private static void WriteFunction(TextWriter writer, Function func, string body) { - writer.WriteLine(memberIndentStr + "{0}{1}({2}){3}", - func.returnType == "" ? "" : func.returnType + " ", + writer.WriteLine( + memberIndentStr + "{0}{1}({2}){3}", + func.returnType == "" ? "" : func.returnType + " ", func.useByName(), string.Join(",", func.formalParameters), - func.specifiers == "" ? "" : " " + func.specifiers); + func.specifiers == "" ? "" : " " + func.specifiers + ); if (func.returnType != "") writer.WriteLine(memberIndentStr + "{"); writer.WriteLine(body); @@ -1269,23 +1835,35 @@ private static void WriteFunction(TextWriter writer, Function func, string body) writer.WriteLine(memberIndentStr + "}"); } - Function getFunction(string baseName, string addName, string[] formalParameters, string[] overloadFormalParameters) + Function getFunction( + string baseName, + string addName, + string[] formalParameters, + string[] overloadFormalParameters + ) { string proposedName; - if (addName == "cont" && baseName.Length>=5 && baseName.Substring(baseName.Length - 5, 4) == "cont") + if ( + addName == "cont" + && baseName.Length >= 5 + && baseName.Substring(baseName.Length - 5, 4) == "cont" + ) proposedName = baseName.Substring(0, baseName.Length - 1); else proposedName = baseName + addName; int i = 0; - while (functions.ContainsKey(string.Format("{0}{1}", proposedName, ++i))) ; + while (functions.ContainsKey(string.Format("{0}{1}", proposedName, ++i))) + ; - var f = new Function { + var f = new Function + { name = string.Format("{0}{1}", proposedName, i), returnType = "int", formalParameters = formalParameters }; - if (overloadFormalParameters != null) { + if (overloadFormalParameters != null) + { f.addOverload(overloadFormalParameters); } f.Indent(codeIndent); @@ -1300,15 +1878,18 @@ Function getFunction(string baseName, string addName, params string[] formalPara string[] ParameterList() { - return actor.parameters.Select(p => - { - // SOMEDAY: pass small built in types by value - if (p.initializer != "") - return string.Format("{0} const& {1} = {2}", p.type, p.name, p.initializer); - else - return string.Format("{0} const& {1}", p.type, p.name); - }).ToArray(); + return actor + .parameters.Select(p => + { + // SOMEDAY: pass small built in types by value + if (p.initializer != "") + return string.Format("{0} const& {1} = {2}", p.type, p.name, p.initializer); + else + return string.Format("{0} const& {1}", p.type, p.name); + }) + .ToArray(); } + void WriteCancelFunc(TextWriter writer) { if (actor.IsCancellable()) @@ -1317,7 +1898,7 @@ void WriteCancelFunc(TextWriter writer) { name = "cancel", returnType = "void", - formalParameters = new string[] {}, + formalParameters = new string[] { }, endIsUnreachable = true, publicName = true, specifiers = "override" @@ -1331,7 +1912,11 @@ void WriteCancelFunc(TextWriter writer) if (cb.CallbackGroup != lastGroup) { lastGroup = cb.CallbackGroup; - cancelFunc.WriteLine("case {0}: this->a_callback_error(({1}*)0, actor_cancelled()); break;", cb.CallbackGroup, cb.type); + cancelFunc.WriteLine( + "case {0}: this->a_callback_error(({1}*)0, actor_cancelled()); break;", + cb.CallbackGroup, + cb.type + ); } cancelFunc.WriteLine("}"); WriteFunction(writer, cancelFunc, cancelFunc.BodyText); @@ -1351,9 +1936,15 @@ void WriteConstructor(Function body, TextWriter writer, string fullStateClassNam // Initializes class member variables constructor.Indent(codeIndent); - constructor.WriteLine( " : Actor<" + (actor.returnType == null ? "void" : actor.returnType) + ">()," ); - constructor.WriteLine( " {0}({1}),", fullStateClassName, string.Join(", ", actor.parameters.Select(p => p.name))); - constructor.WriteLine( " activeActorHelper(__actorIdentifier)"); + constructor.WriteLine( + " : Actor<" + (actor.returnType == null ? "void" : actor.returnType) + ">()," + ); + constructor.WriteLine( + " {0}({1}),", + fullStateClassName, + string.Join(", ", actor.parameters.Select(p => p.name)) + ); + constructor.WriteLine(" activeActorHelper(__actorIdentifier)"); constructor.Indent(-1); constructor.WriteLine("{"); @@ -1415,7 +2006,8 @@ void WriteStateConstructor(TextWriter writer) WriteFunction(writer, constructor, constructor.BodyText); } - void WriteStateDestructor(TextWriter writer) { + void WriteStateDestructor(TextWriter writer) + { Function destructor = new Function { name = String.Format("~{0}", stateClassName), @@ -1434,27 +2026,36 @@ void WriteStateDestructor(TextWriter writer) { IEnumerable Flatten(Statement stmt) { - if (stmt == null) return new Statement[] { }; + if (stmt == null) + return new Statement[] { }; var fl = new TypeSwitch>(stmt) .Case(s => Flatten(s.body)) .Case(s => Flatten(s.body)) .Case(s => Flatten(s.body)) .Case(s => Flatten(s.body)) - .Case(s => s.statements.SelectMany(t=>Flatten(t))) - .Case( s => Flatten(s.ifBody).Concat(Flatten(s.elseBody)) ) - .Case( s => Flatten(s.body) ) - .Case( s => Flatten(s.body) ) - .Case( s => Flatten(s.tryBody).Concat( s.catches.SelectMany(c=>Flatten(c.body)) ) ) + .Case(s => s.statements.SelectMany(t => Flatten(t))) + .Case(s => Flatten(s.ifBody).Concat(Flatten(s.elseBody))) + .Case(s => Flatten(s.body)) + .Case(s => Flatten(s.body)) + .Case(s => + Flatten(s.tryBody).Concat(s.catches.SelectMany(c => Flatten(c.body))) + ) .Case(s => Enumerable.Empty()) .Return(); - return new Statement[]{stmt}.Concat(fl); + return new Statement[] { stmt }.Concat(fl); } void FindState() { - state = actor.parameters - .Select( - p=>new StateVar { SourceLine = actor.SourceLine, name=p.name, type=p.type, initializer=p.name, initializerConstructorSyntax=false } ) + state = actor + .parameters.Select(p => new StateVar + { + SourceLine = actor.SourceLine, + name = p.name, + type = p.type, + initializer = p.name, + initializerConstructorSyntax = false + }) .ToList(); } diff --git a/flow/actorcompiler/ActorParser.cs b/flow/actorcompiler/ActorParser.cs index 7eb5cf53cb0..64af3e4f40b 100644 --- a/flow/actorcompiler/ActorParser.cs +++ b/flow/actorcompiler/ActorParser.cs @@ -28,8 +28,9 @@ namespace actorcompiler class Error : Exception { public int SourceLine { get; private set; } + public Error(int SourceLine, string format, params object[] args) - : base(string.Format(format,args)) + : base(string.Format(format, args)) { this.SourceLine = SourceLine; } @@ -38,15 +39,23 @@ public Error(int SourceLine, string format, params object[] args) class ErrorMessagePolicy { public bool DisableDiagnostics = false; + public void HandleActorWithoutWait(String sourceFile, Actor actor) { if (!DisableDiagnostics && !actor.isTestCase) { // TODO(atn34): Once cmake is the only build system we can make this an error instead of a warning. - Console.Error.WriteLine("{0}:{1}: warning: ACTOR {2} does not contain a wait() statement", sourceFile, actor.SourceLine, actor.name); + Console.Error.WriteLine( + "{0}:{1}: warning: ACTOR {2} does not contain a wait() statement", + sourceFile, + actor.SourceLine, + actor.name + ); } } - public bool ActorsNoDiscardByDefault() { + + public bool ActorsNoDiscardByDefault() + { return !DisableDiagnostics; } } @@ -58,52 +67,91 @@ class Token public int SourceLine; public int BraceDepth; public int ParenDepth; - public bool IsWhitespace { get { return Value == " " || Value == "\n" || Value == "\r" || Value == "\r\n" || Value == "\t" || Value.StartsWith("//") || Value.StartsWith("/*"); } } - public override string ToString() { return Value; } + public bool IsWhitespace + { + get + { + return Value == " " + || Value == "\n" + || Value == "\r" + || Value == "\r\n" + || Value == "\t" + || Value.StartsWith("//") + || Value.StartsWith("/*"); + } + } + + public override string ToString() + { + return Value; + } + public Token Assert(string error, Func pred) { - if (!pred(this)) throw new Error(SourceLine, error); + if (!pred(this)) + throw new Error(SourceLine, error); return this; } + public TokenRange GetMatchingRangeIn(TokenRange range) { - Func pred; + Func pred; int dir; - switch (Value) { - case "(": pred = t=> t.Value != ")" || t.ParenDepth != ParenDepth; dir = +1; break; - case ")": pred = t=> t.Value != "(" || t.ParenDepth != ParenDepth; dir = -1; break; - case "{": pred = t=> t.Value != "}" || t.BraceDepth != BraceDepth; dir = +1; break; - case "}": pred = t=> t.Value != "{" || t.BraceDepth != BraceDepth; dir = -1; break; - case "<": return - new TokenRange(range.GetAllTokens(), - Position+1, - AngleBracketParser.NotInsideAngleBrackets( - new TokenRange(range.GetAllTokens(), Position, range.End)) - .Skip(1) // skip the "<", which is considered "outside" - .First() // get the ">", which is likewise "outside" - .Position); - case "[": return - new TokenRange(range.GetAllTokens(), - Position+1, - BracketParser.NotInsideBrackets( - new TokenRange(range.GetAllTokens(), Position, range.End)) - .Skip(1) // skip the "[", which is considered "outside" - .First() // get the "]", which is likewise "outside" - .Position); - default: throw new NotSupportedException("Can't match this token!"); + switch (Value) + { + case "(": + pred = t => t.Value != ")" || t.ParenDepth != ParenDepth; + dir = +1; + break; + case ")": + pred = t => t.Value != "(" || t.ParenDepth != ParenDepth; + dir = -1; + break; + case "{": + pred = t => t.Value != "}" || t.BraceDepth != BraceDepth; + dir = +1; + break; + case "}": + pred = t => t.Value != "{" || t.BraceDepth != BraceDepth; + dir = -1; + break; + case "<": + return new TokenRange( + range.GetAllTokens(), + Position + 1, + AngleBracketParser + .NotInsideAngleBrackets( + new TokenRange(range.GetAllTokens(), Position, range.End) + ) + .Skip(1) // skip the "<", which is considered "outside" + .First() // get the ">", which is likewise "outside" + .Position + ); + case "[": + return new TokenRange( + range.GetAllTokens(), + Position + 1, + BracketParser + .NotInsideBrackets( + new TokenRange(range.GetAllTokens(), Position, range.End) + ) + .Skip(1) // skip the "[", which is considered "outside" + .First() // get the "]", which is likewise "outside" + .Position + ); + default: + throw new NotSupportedException("Can't match this token!"); } TokenRange r; if (dir == -1) { - r = new TokenRange(range.GetAllTokens(), range.Begin, Position) - .RevTakeWhile(pred); + r = new TokenRange(range.GetAllTokens(), range.Begin, Position).RevTakeWhile(pred); if (r.Begin == range.Begin) throw new Error(SourceLine, "Syntax error: Unmatched " + Value); } else { - r = new TokenRange(range.GetAllTokens(), Position+1, range.End) - .TakeWhile(pred); + r = new TokenRange(range.GetAllTokens(), Position + 1, range.End).TakeWhile(pred); if (r.End == range.End) throw new Error(SourceLine, "Syntax error: Unmatched " + Value); } @@ -115,23 +163,40 @@ class TokenRange : IEnumerable { public TokenRange(Token[] tokens, int beginPos, int endPos) { - if (beginPos > endPos) throw new InvalidOperationException("Invalid TokenRange"); + if (beginPos > endPos) + throw new InvalidOperationException("Invalid TokenRange"); this.tokens = tokens; this.beginPos = beginPos; this.endPos = endPos; } - public bool IsEmpty { get { return beginPos==endPos; } } - public int Begin { get { return beginPos; } } - public int End { get { return endPos; } } - public Token First() { - if (beginPos == endPos) throw new InvalidOperationException("Empty TokenRange"); - return tokens[beginPos]; + public bool IsEmpty + { + get { return beginPos == endPos; } + } + public int Begin + { + get { return beginPos; } + } + public int End + { + get { return endPos; } + } + + public Token First() + { + if (beginPos == endPos) + throw new InvalidOperationException("Empty TokenRange"); + return tokens[beginPos]; } - public Token Last() { - if (beginPos == endPos) throw new InvalidOperationException("Empty TokenRange"); + + public Token Last() + { + if (beginPos == endPos) + throw new InvalidOperationException("Empty TokenRange"); return tokens[endPos - 1]; } + public Token Last(Func pred) { for (int i = endPos - 1; i >= beginPos; i--) @@ -139,26 +204,35 @@ public Token Last(Func pred) return tokens[i]; throw new Exception("Matching token not found"); } + public TokenRange Skip(int count) { return new TokenRange(tokens, beginPos + count, endPos); } + public TokenRange Consume(string value) { First().Assert("Expected " + value, t => t.Value == value); return Skip(1); } + public TokenRange Consume(string error, Func pred) { First().Assert(error, pred); return Skip(1); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + public IEnumerator GetEnumerator() { for (int i = beginPos; i < endPos; i++) yield return tokens[i]; } + public TokenRange SkipWhile(Func pred) { for (int e = beginPos; e < endPos; e++) @@ -166,6 +240,7 @@ public TokenRange SkipWhile(Func pred) return new TokenRange(tokens, e, endPos); return new TokenRange(tokens, endPos, endPos); } + public TokenRange TakeWhile(Func pred) { for (int e = beginPos; e < endPos; e++) @@ -173,13 +248,15 @@ public TokenRange TakeWhile(Func pred) return new TokenRange(tokens, beginPos, e); return new TokenRange(tokens, beginPos, endPos); } + public TokenRange RevTakeWhile(Func pred) { - for (int e = endPos-1; e >= beginPos; e--) + for (int e = endPos - 1; e >= beginPos; e--) if (!pred(tokens[e])) - return new TokenRange(tokens, e+1, endPos); + return new TokenRange(tokens, e + 1, endPos); return new TokenRange(tokens, beginPos, endPos); } + public TokenRange RevSkipWhile(Func pred) { for (int e = endPos - 1; e >= beginPos; e--) @@ -187,12 +264,15 @@ public TokenRange RevSkipWhile(Func pred) return new TokenRange(tokens, beginPos, e + 1); return new TokenRange(tokens, beginPos, beginPos); } - public Token[] GetAllTokens() { return tokens; } - public int Length { - get { - return endPos - beginPos; - } + public Token[] GetAllTokens() + { + return tokens; + } + + public int Length + { + get { return endPos - beginPos; } } Token[] tokens; @@ -208,14 +288,18 @@ public static IEnumerable NotInsideBrackets(IEnumerable tokens) int? BasePD = null; foreach (var tok in tokens) { - if (BasePD == null) BasePD = tok.ParenDepth; - if (tok.ParenDepth == BasePD && tok.Value == "]") BracketDepth--; + if (BasePD == null) + BasePD = tok.ParenDepth; + if (tok.ParenDepth == BasePD && tok.Value == "]") + BracketDepth--; if (BracketDepth == 0) yield return tok; - if (tok.ParenDepth == BasePD && tok.Value == "[") BracketDepth++; + if (tok.ParenDepth == BasePD && tok.Value == "[") + BracketDepth++; } } }; + static class AngleBracketParser { public static IEnumerable NotInsideAngleBrackets(IEnumerable tokens) @@ -224,11 +308,14 @@ public static IEnumerable NotInsideAngleBrackets(IEnumerable token int? BasePD = null; foreach (var tok in tokens) { - if (BasePD == null) BasePD = tok.ParenDepth; - if (tok.ParenDepth == BasePD && tok.Value == ">") AngleDepth--; + if (BasePD == null) + BasePD = tok.ParenDepth; + if (tok.ParenDepth == BasePD && tok.Value == ">") + AngleDepth--; if (AngleDepth == 0) yield return tok; - if (tok.ParenDepth == BasePD && tok.Value == "<") AngleDepth++; + if (tok.ParenDepth == BasePD && tok.Value == "<") + AngleDepth++; } } }; @@ -243,20 +330,26 @@ class ActorParser public bool generateProbes; public Dictionary<(ulong, ulong), string> uidObjects { get; private set; } - public ActorParser(string text, string sourceFile, ErrorMessagePolicy errorMessagePolicy, bool generateProbes) + public ActorParser( + string text, + string sourceFile, + ErrorMessagePolicy errorMessagePolicy, + bool generateProbes + ) { this.sourceFile = sourceFile; this.errorMessagePolicy = errorMessagePolicy; this.generateProbes = generateProbes; this.uidObjects = new Dictionary<(ulong, ulong), string>(); - tokens = Tokenize(text).Select(t=>new Token{ Value=t }).ToArray(); + tokens = Tokenize(text).Select(t => new Token { Value = t }).ToArray(); CountParens(); //if (sourceFile.EndsWith(".h")) LineNumbersEnabled = false; //Console.WriteLine("{0} chars -> {1} tokens", text.Length, tokens.Length); //showTokens(); } - class ClassContext { + class ClassContext + { public string name; public int inBlocks; } @@ -295,26 +388,34 @@ private bool ParseClassContext(TokenRange toks, out string name) // http://nongnu.org/hcb/#class-head-name first = toks.First(NonWhitespace); - if (!identifierPattern.Match(first.Value).Success) { + if (!identifierPattern.Match(first.Value).Success) + { return false; } - while (true) { - first.Assert("Expected identifier", t=>identifierPattern.Match(t.Value).Success); + while (true) + { + first.Assert("Expected identifier", t => identifierPattern.Match(t.Value).Success); name += first.Value; toks = range(first.Position + 1, toks.End); - if (toks.First(NonWhitespace).Value == "::") { + if (toks.First(NonWhitespace).Value == "::") + { name += "::"; toks = toks.SkipWhile(Whitespace).Skip(1); - } else { + } + else + { break; } first = toks.First(NonWhitespace); } // http://nongnu.org/hcb/#class-virt-specifier-seq - toks = toks.SkipWhile(t => Whitespace(t) || t.Value == "final" || t.Value == "explicit"); + toks = toks.SkipWhile(t => + Whitespace(t) || t.Value == "final" || t.Value == "explicit" + ); first = toks.First(NonWhitespace); - if (first.Value == ":" || first.Value == "{") { + if (first.Value == ":" || first.Value == "{") + { // At this point we've confirmed that this is a class. return true; } @@ -333,9 +434,9 @@ public void Write(System.IO.TextWriter writer, string destFileName) } int inBlocks = 0; Stack classContextStack = new Stack(); - for(int i=0; i 0) { - actor.enclosingClass = String.Join("::", classContextStack.Reverse().Select(t => t.name)); + actor.enclosingClass = String.Join( + "::", + classContextStack.Reverse().Select(t => t.name) + ); } var actorWriter = new System.IO.StringWriter(); actorWriter.NewLine = "\n"; - var actorCompiler = new ActorCompiler(actor, sourceFile, inBlocks == 0, LineNumbersEnabled, generateProbes); + var actorCompiler = new ActorCompiler( + actor, + sourceFile, + inBlocks == 0, + LineNumbersEnabled, + generateProbes + ); actorCompiler.Write(actorWriter); - actorCompiler.uidObjects.ToList().ForEach(x => this.uidObjects.TryAdd(x.Key, x.Value)); + actorCompiler + .uidObjects.ToList() + .ForEach(x => this.uidObjects.TryAdd(x.Key, x.Value)); string[] actorLines = actorWriter.ToString().Split('\n'); @@ -361,16 +473,21 @@ public void Write(System.IO.TextWriter writer, string destFileName) if (LineNumbersEnabled) { bool isLineNumber = line.Contains("#line"); - if (isLineNumber) hadLineNumber = true; + if (isLineNumber) + hadLineNumber = true; if (!isLineNumber && !hasLineNumber && hadLineNumber) { - writer.WriteLine("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t#line {0} \"{1}\"", outLine + 1, destFileName); + writer.WriteLine( + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t#line {0} \"{1}\"", + outLine + 1, + destFileName + ); outLine++; hadLineNumber = false; } hasLineNumber = isLineNumber; } - writer.WriteLine(line.TrimEnd('\n','\r')); + writer.WriteLine(line.TrimEnd('\n', '\r')); outLine++; } @@ -395,13 +512,19 @@ public void Write(System.IO.TextWriter writer, string destFileName) outLine++; } } - else if (tokens[i].Value == "class" || tokens[i].Value == "struct" || tokens[i].Value == "union") + else if ( + tokens[i].Value == "class" + || tokens[i].Value == "struct" + || tokens[i].Value == "union" + ) { writer.Write(tokens[i].Value); string name; - if (ParseClassContext(range(i+1, tokens.Length), out name)) + if (ParseClassContext(range(i + 1, tokens.Length), out name)) { - classContextStack.Push(new ClassContext { name = name, inBlocks = inBlocks}); + classContextStack.Push( + new ClassContext { name = name, inBlocks = inBlocks } + ); } } else @@ -413,7 +536,10 @@ public void Write(System.IO.TextWriter writer, string destFileName) else if (tokens[i].Value == "}") { inBlocks--; - if (classContextStack.Count > 0 && classContextStack.Peek().inBlocks == inBlocks) + if ( + classContextStack.Count > 0 + && classContextStack.Peek().inBlocks == inBlocks + ) { classContextStack.Pop(); } @@ -424,13 +550,20 @@ public void Write(System.IO.TextWriter writer, string destFileName) } } - IEnumerable SplitParameterList( TokenRange toks, string delimiter ) { - if (toks.Begin==toks.End) yield break; - while (true) { - Token comma = AngleBracketParser.NotInsideAngleBrackets( toks ) - .FirstOrDefault( t=> t.Value==delimiter && t.ParenDepth == toks.First().ParenDepth ); - if (comma == null) break; - yield return range(toks.Begin,comma.Position); + IEnumerable SplitParameterList(TokenRange toks, string delimiter) + { + if (toks.Begin == toks.End) + yield break; + while (true) + { + Token comma = AngleBracketParser + .NotInsideAngleBrackets(toks) + .FirstOrDefault(t => + t.Value == delimiter && t.ParenDepth == toks.First().ParenDepth + ); + if (comma == null) + break; + yield return range(toks.Begin, comma.Position); toks = range(comma.Position + 1, toks.End); } yield return toks; @@ -444,7 +577,8 @@ IEnumerable NormalizeWhitespace(IEnumerable tokens) { if (!tok.IsWhitespace) { - if (inWhitespace && !leading) yield return new Token { Value = " " }; + if (inWhitespace && !leading) + yield return new Token { Value = " " }; inWhitespace = false; yield return tok; leading = false; @@ -456,41 +590,52 @@ IEnumerable NormalizeWhitespace(IEnumerable tokens) } } - void ParseDeclaration(TokenRange tokens, - out Token name, + void ParseDeclaration( + TokenRange tokens, + out Token name, out TokenRange type, - out TokenRange initializer, - out bool constructorSyntax) + out TokenRange initializer, + out bool constructorSyntax + ) { initializer = null; TokenRange beforeInitializer = tokens; constructorSyntax = false; - Token equals = AngleBracketParser.NotInsideAngleBrackets(tokens) + Token equals = AngleBracketParser + .NotInsideAngleBrackets(tokens) .FirstOrDefault(t => t.Value == "=" && t.ParenDepth == tokens.First().ParenDepth); if (equals != null) { // type name = initializer; - beforeInitializer = range(tokens.Begin,equals.Position); + beforeInitializer = range(tokens.Begin, equals.Position); initializer = range(equals.Position + 1, tokens.End); } else { - Token paren = AngleBracketParser.NotInsideAngleBrackets(tokens) + Token paren = AngleBracketParser + .NotInsideAngleBrackets(tokens) .FirstOrDefault(t => t.Value == "("); if (paren != null) { // type name(initializer); constructorSyntax = true; beforeInitializer = range(tokens.Begin, paren.Position); - initializer = - range(paren.Position + 1, tokens.End) - .TakeWhile(t => t.ParenDepth > paren.ParenDepth); - } else { - Token brace = AngleBracketParser.NotInsideAngleBrackets(tokens).FirstOrDefault(t => t.Value == "{"); - if (brace != null) { + initializer = range(paren.Position + 1, tokens.End) + .TakeWhile(t => t.ParenDepth > paren.ParenDepth); + } + else + { + Token brace = AngleBracketParser + .NotInsideAngleBrackets(tokens) + .FirstOrDefault(t => t.Value == "{"); + if (brace != null) + { // type name{initializer}; - throw new Error(brace.SourceLine, "Uniform initialization syntax is not currently supported for state variables (use '(' instead of '}}' ?)"); + throw new Error( + brace.SourceLine, + "Uniform initialization syntax is not currently supported for state variables (use '(' instead of '}}' ?)" + ); } } } @@ -503,9 +648,10 @@ void ParseDeclaration(TokenRange tokens, VarDeclaration ParseVarDeclaration(TokenRange tokens) { Token name; - TokenRange type, initializer; + TokenRange type, + initializer; bool constructorSyntax; - ParseDeclaration( tokens, out name, out type, out initializer, out constructorSyntax ); + ParseDeclaration(tokens, out name, out type, out initializer, out constructorSyntax); return new VarDeclaration { name = name.Value, @@ -538,13 +684,18 @@ void ParseTestCaseHeading(Actor actor, TokenRange toks) // The parameter(s) to the TEST_CASE macro are opaque to the actor compiler TokenRange paramRange = toks.Last(NonWhitespace) - .Assert("Unexpected tokens after test case parameter list.", - t => t.Value == ")" && t.ParenDepth == toks.First().ParenDepth) + .Assert( + "Unexpected tokens after test case parameter list.", + t => t.Value == ")" && t.ParenDepth == toks.First().ParenDepth + ) .GetMatchingRangeIn(toks); actor.testCaseParameters = str(paramRange); actor.name = "flowTestCase" + toks.First().SourceLine; - actor.parameters = new VarDeclaration[] { new VarDeclaration { + actor.parameters = new VarDeclaration[] + { + new VarDeclaration + { name = "params", type = "UnitTestParameters", initializer = "", @@ -559,13 +710,13 @@ void ParseActorHeading(Actor actor, TokenRange toks) var template = toks.First(NonWhitespace); if (template.Value == "template") { - var templateParams = range(template.Position+1, toks.End) + var templateParams = range(template.Position + 1, toks.End) .First(NonWhitespace) - .Assert("Invalid template declaration", t=>t.Value=="<") + .Assert("Invalid template declaration", t => t.Value == "<") .GetMatchingRangeIn(toks); actor.templateFormals = SplitParameterList(templateParams, ",") - .Select(p => ParseVarDeclaration(p)) //< SOMEDAY: ? + .Select(p => ParseVarDeclaration(p)) //< SOMEDAY: ? .ToArray(); toks = range(templateParams.End + 1, toks.End); @@ -576,7 +727,11 @@ void ParseActorHeading(Actor actor, TokenRange toks) var attributeContents = attribute.GetMatchingRangeIn(toks); var asArray = attributeContents.ToArray(); - if (asArray.Length < 2 || asArray[0].Value != "[" || asArray[asArray.Length - 1].Value != "]") + if ( + asArray.Length < 2 + || asArray[0].Value != "[" + || asArray[asArray.Length - 1].Value != "]" + ) { throw new Error(actor.SourceLine, "Invalid attribute: Expected [[...]]"); } @@ -602,14 +757,16 @@ void ParseActorHeading(Actor actor, TokenRange toks) // Find the parameter list TokenRange paramRange = toks.Last(NonWhitespace) - .Assert("Unexpected tokens after actor parameter list.", - t => t.Value == ")" && t.ParenDepth == toks.First().ParenDepth) + .Assert( + "Unexpected tokens after actor parameter list.", + t => t.Value == ")" && t.ParenDepth == toks.First().ParenDepth + ) .GetMatchingRangeIn(toks); actor.parameters = SplitParameterList(paramRange, ",") .Select(p => ParseVarDeclaration(p)) .ToArray(); - var name = range(toks.Begin,paramRange.Begin-1).Last(NonWhitespace); + var name = range(toks.Begin, paramRange.Begin - 1).Last(NonWhitespace); actor.name = name.Value; // SOMEDAY: refactor? @@ -617,11 +774,17 @@ void ParseActorHeading(Actor actor, TokenRange toks) var retToken = returnType.First(); if (retToken.Value == "Future") { - var ofType = returnType.Skip(1).First(NonWhitespace).Assert("Expected <", tok => tok.Value == "<").GetMatchingRangeIn(returnType); + var ofType = returnType + .Skip(1) + .First(NonWhitespace) + .Assert("Expected <", tok => tok.Value == "<") + .GetMatchingRangeIn(returnType); actor.returnType = str(NormalizeWhitespace(ofType)); toks = range(ofType.End + 1, returnType.End); } - else if (retToken.Value == "void"/* && !returnType.Skip(1).Any(NonWhitespace)*/) + else if ( + retToken.Value == "void" /* && !returnType.Skip(1).Any(NonWhitespace)*/ + ) { actor.returnType = null; toks = returnType.Skip(1); @@ -638,11 +801,23 @@ void ParseActorHeading(Actor actor, TokenRange toks) } else { - Console.WriteLine("Tokens: '{0}' {1} '{2}'", str(toks), toks.Count(), toks.Last().Value); - throw new Error(actor.SourceLine, "Unrecognized tokens preceding parameter list in actor declaration"); + Console.WriteLine( + "Tokens: '{0}' {1} '{2}'", + str(toks), + toks.Count(), + toks.Last().Value + ); + throw new Error( + actor.SourceLine, + "Unrecognized tokens preceding parameter list in actor declaration" + ); } } - if (errorMessagePolicy.ActorsNoDiscardByDefault() && !actor.attributes.Contains("[[flow_allow_discard]]")) { + if ( + errorMessagePolicy.ActorsNoDiscardByDefault() + && !actor.attributes.Contains("[[flow_allow_discard]]") + ) + { if (actor.IsCancellable()) { actor.attributes.Add("[[nodiscard]]"); @@ -650,8 +825,10 @@ void ParseActorHeading(Actor actor, TokenRange toks) } HashSet knownFlowAttributes = new HashSet(); knownFlowAttributes.Add("[[flow_allow_discard]]"); - foreach (var flowAttribute in actor.attributes.Where(a => a.StartsWith("[[flow_"))) { - if (!knownFlowAttributes.Contains(flowAttribute)) { + foreach (var flowAttribute in actor.attributes.Where(a => a.StartsWith("[[flow_"))) + { + if (!knownFlowAttributes.Contains(flowAttribute)) + { throw new Error(actor.SourceLine, "Unknown flow attribute {0}", flowAttribute); } } @@ -660,58 +837,46 @@ void ParseActorHeading(Actor actor, TokenRange toks) LoopStatement ParseLoopStatement(TokenRange toks) { - return new LoopStatement { - body = ParseCompoundStatement( toks.Consume("loop") ) - }; + return new LoopStatement { body = ParseCompoundStatement(toks.Consume("loop")) }; } ChooseStatement ParseChooseStatement(TokenRange toks) { - return new ChooseStatement - { - body = ParseCompoundStatement(toks.Consume("choose")) - }; + return new ChooseStatement { body = ParseCompoundStatement(toks.Consume("choose")) }; } WhenStatement ParseWhenStatement(TokenRange toks) { var expr = toks.Consume("when") - .SkipWhile(Whitespace) - .First() - .Assert("Expected (", t => t.Value == "(") - .GetMatchingRangeIn(toks) - .SkipWhile(Whitespace); + .SkipWhile(Whitespace) + .First() + .Assert("Expected (", t => t.Value == "(") + .GetMatchingRangeIn(toks) + .SkipWhile(Whitespace); - return new WhenStatement { + return new WhenStatement + { wait = ParseWaitStatement(expr), - body = ParseCompoundStatement(range(expr.End+1, toks.End)) + body = ParseCompoundStatement(range(expr.End + 1, toks.End)) }; } StateDeclarationStatement ParseStateDeclaration(TokenRange toks) { toks = toks.Consume("state").RevSkipWhile(t => t.Value == ";"); - return new StateDeclarationStatement { - decl = ParseVarDeclaration(toks) - }; + return new StateDeclarationStatement { decl = ParseVarDeclaration(toks) }; } ReturnStatement ParseReturnStatement(TokenRange toks) { toks = toks.Consume("return").RevSkipWhile(t => t.Value == ";"); - return new ReturnStatement - { - expression = str(NormalizeWhitespace(toks)) - }; + return new ReturnStatement { expression = str(NormalizeWhitespace(toks)) }; } ThrowStatement ParseThrowStatement(TokenRange toks) { toks = toks.Consume("throw").RevSkipWhile(t => t.Value == ";"); - return new ThrowStatement - { - expression = str(NormalizeWhitespace(toks)) - }; + return new ThrowStatement { expression = str(NormalizeWhitespace(toks)) }; } WaitStatement ParseWaitStatement(TokenRange toks) @@ -726,22 +891,35 @@ WaitStatement ParseWaitStatement(TokenRange toks) TokenRange initializer; if (toks.First().Value == "wait" || toks.First().Value == "waitNext") { - initializer = toks.RevSkipWhile(t=>t.Value==";"); - ws.result = new VarDeclaration { - name = "_", - type = "Void", - initializer = "", - initializerConstructorSyntax = false + initializer = toks.RevSkipWhile(t => t.Value == ";"); + ws.result = new VarDeclaration + { + name = "_", + type = "Void", + initializer = "", + initializerConstructorSyntax = false }; - } else { + } + else + { Token name; TokenRange type; bool constructorSyntax; - ParseDeclaration( toks.RevSkipWhile(t=>t.Value==";"), out name, out type, out initializer, out constructorSyntax ); + ParseDeclaration( + toks.RevSkipWhile(t => t.Value == ";"), + out name, + out type, + out initializer, + out constructorSyntax + ); string typestring = str(NormalizeWhitespace(type)); - if (typestring == "Void") { - throw new Error(ws.FirstSourceLine, "Assigning the result of a Void wait is not allowed. Just use a standalone wait statement."); + if (typestring == "Void") + { + throw new Error( + ws.FirstSourceLine, + "Assigning the result of a Void wait is not allowed. Just use a standalone wait statement." + ); } ws.result = new VarDeclaration @@ -753,19 +931,38 @@ WaitStatement ParseWaitStatement(TokenRange toks) }; } - if (initializer == null) throw new Error(ws.FirstSourceLine, "Wait statement must be a declaration or standalone statement"); + if (initializer == null) + throw new Error( + ws.FirstSourceLine, + "Wait statement must be a declaration or standalone statement" + ); var waitParams = initializer - .SkipWhile(Whitespace).Consume("Statement contains a wait, but is not a valid wait statement or a supported compound statement.1", - t=> { - if (t.Value=="wait") return true; - if (t.Value=="waitNext") { ws.isWaitNext = true; return true; } - return false; - }) - .SkipWhile(Whitespace).First().Assert("Expected (", t => t.Value == "(") + .SkipWhile(Whitespace) + .Consume( + "Statement contains a wait, but is not a valid wait statement or a supported compound statement.1", + t => + { + if (t.Value == "wait") + return true; + if (t.Value == "waitNext") + { + ws.isWaitNext = true; + return true; + } + return false; + } + ) + .SkipWhile(Whitespace) + .First() + .Assert("Expected (", t => t.Value == "(") .GetMatchingRangeIn(initializer); - if (!range(waitParams.End, initializer.End).Consume(")").All(Whitespace)) { - throw new Error(toks.First().SourceLine, "Statement contains a wait, but is not a valid wait statement or a supported compound statement.2"); + if (!range(waitParams.End, initializer.End).Consume(")").All(Whitespace)) + { + throw new Error( + toks.First().SourceLine, + "Statement contains a wait, but is not a valid wait statement or a supported compound statement.2" + ); } ws.futureExpression = str(NormalizeWhitespace(waitParams)); @@ -775,9 +972,9 @@ WaitStatement ParseWaitStatement(TokenRange toks) WhileStatement ParseWhileStatement(TokenRange toks) { var expr = toks.Consume("while") - .First(NonWhitespace) - .Assert("Expected (", t => t.Value == "(") - .GetMatchingRangeIn(toks); + .First(NonWhitespace) + .Assert("Expected (", t => t.Value == "(") + .GetMatchingRangeIn(toks); return new WhileStatement { expression = str(NormalizeWhitespace(expr)), @@ -787,18 +984,17 @@ WhileStatement ParseWhileStatement(TokenRange toks) Statement ParseForStatement(TokenRange toks) { - var head = - toks.Consume("for") - .First(NonWhitespace) - .Assert("Expected (", t => t.Value == "(") - .GetMatchingRangeIn(toks); + var head = toks.Consume("for") + .First(NonWhitespace) + .Assert("Expected (", t => t.Value == "(") + .GetMatchingRangeIn(toks); - Token[] delim = - head.Where( - t => t.ParenDepth == head.First().ParenDepth && - t.BraceDepth == head.First().BraceDepth && - t.Value==";" - ).ToArray(); + Token[] delim = head.Where(t => + t.ParenDepth == head.First().ParenDepth + && t.BraceDepth == head.First().BraceDepth + && t.Value == ";" + ) + .ToArray(); if (delim.Length == 2) { var init = range(head.Begin, delim[0].Position); @@ -815,23 +1011,34 @@ Statement ParseForStatement(TokenRange toks) }; } - delim = - head.Where( - t => t.ParenDepth == head.First().ParenDepth && - t.BraceDepth == head.First().BraceDepth && - t.Value == ":" - ).ToArray(); + delim = head.Where(t => + t.ParenDepth == head.First().ParenDepth + && t.BraceDepth == head.First().BraceDepth + && t.Value == ":" + ) + .ToArray(); if (delim.Length != 1) { - throw new Error(head.First().SourceLine, "for statement must be 3-arg style or c++11 2-arg style"); + throw new Error( + head.First().SourceLine, + "for statement must be 3-arg style or c++11 2-arg style" + ); } return new RangeForStatement { // The container over which to iterate - rangeExpression = str(NormalizeWhitespace(range(delim[0].Position + 1, head.End).SkipWhile(Whitespace))), + rangeExpression = str( + NormalizeWhitespace( + range(delim[0].Position + 1, head.End).SkipWhile(Whitespace) + ) + ), // Type and name of the variable assigned in each iteration - rangeDecl = str(NormalizeWhitespace(range(head.Begin, delim[0].Position - 1).SkipWhile(Whitespace))), + rangeDecl = str( + NormalizeWhitespace( + range(head.Begin, delim[0].Position - 1).SkipWhile(Whitespace) + ) + ), // The body of the for loop body = ParseCompoundStatement(range(head.End + 1, toks.End)) }; @@ -842,20 +1049,23 @@ Statement ParseIfStatement(TokenRange toks) toks = toks.Consume("if"); toks = toks.SkipWhile(Whitespace); bool constexpr = toks.First().Value == "constexpr"; - if(constexpr) { - toks = toks.Consume("constexpr").SkipWhile(Whitespace); + if (constexpr) + { + toks = toks.Consume("constexpr").SkipWhile(Whitespace); } var expr = toks.First(NonWhitespace) - .Assert("Expected (", t => t.Value == "(") - .GetMatchingRangeIn(toks); - return new IfStatement { + .Assert("Expected (", t => t.Value == "(") + .GetMatchingRangeIn(toks); + return new IfStatement + { expression = str(NormalizeWhitespace(expr)), constexpr = constexpr, - ifBody = ParseCompoundStatement(range(expr.End+1, toks.End)) + ifBody = ParseCompoundStatement(range(expr.End + 1, toks.End)) // elseBody will be filled in later if necessary by ParseElseStatement }; } + void ParseElseStatement(TokenRange toks, Statement prevStatement) { var ifStatement = prevStatement as IfStatement; @@ -871,9 +1081,10 @@ Statement ParseTryStatement(TokenRange toks) return new TryStatement { tryBody = ParseCompoundStatement(toks.Consume("try")), - catches = new List() // will be filled in later by ParseCatchStatement + catches = new List() // will be filled in later by ParseCatchStatement }; } + void ParseCatchStatement(TokenRange toks, Statement prevStatement) { var tryStatement = prevStatement as TryStatement; @@ -889,17 +1100,27 @@ void ParseCatchStatement(TokenRange toks, Statement prevStatement) expression = str(NormalizeWhitespace(expr)), body = ParseCompoundStatement(range(expr.End + 1, toks.End)), FirstSourceLine = expr.First().SourceLine - }); + } + ); } - static readonly HashSet IllegalKeywords = new HashSet { "goto", "do", "finally", "__if_exists", "__if_not_exists" }; + static readonly HashSet IllegalKeywords = new HashSet + { + "goto", + "do", + "finally", + "__if_exists", + "__if_not_exists" + }; void ParseDeclaration(TokenRange toks, List declarations) { Declaration dec = new Declaration(); Token delim = toks.First(t => t.Value == ";"); - var nameRange = range(toks.Begin, delim.Position).RevSkipWhile(Whitespace).RevTakeWhile(NonWhitespace); + var nameRange = range(toks.Begin, delim.Position) + .RevSkipWhile(Whitespace) + .RevTakeWhile(NonWhitespace); var typeRange = range(toks.Begin, nameRange.Begin); var commentRange = range(delim.Position + 1, toks.End); @@ -922,51 +1143,101 @@ void ParseStatement(TokenRange toks, List statements) switch (toks.First().Value) { - case "loop": Add(ParseLoopStatement(toks)); break; - case "while": Add(ParseWhileStatement(toks)); break; - case "for": Add(ParseForStatement(toks)); break; - case "break": Add(new BreakStatement()); break; - case "continue": Add(new ContinueStatement()); break; - case "return": Add(ParseReturnStatement(toks)); break; - case "{": Add(ParseCompoundStatement(toks)); break; - case "if": Add(ParseIfStatement(toks)); break; - case "else": ParseElseStatement(toks, statements[statements.Count - 1]); break; - case "choose": Add(ParseChooseStatement(toks)); break; - case "when": Add(ParseWhenStatement(toks)); break; - case "try": Add(ParseTryStatement(toks)); break; - case "catch": ParseCatchStatement(toks, statements[statements.Count - 1]); break; - case "throw": Add(ParseThrowStatement(toks)); break; + case "loop": + Add(ParseLoopStatement(toks)); + break; + case "while": + Add(ParseWhileStatement(toks)); + break; + case "for": + Add(ParseForStatement(toks)); + break; + case "break": + Add(new BreakStatement()); + break; + case "continue": + Add(new ContinueStatement()); + break; + case "return": + Add(ParseReturnStatement(toks)); + break; + case "{": + Add(ParseCompoundStatement(toks)); + break; + case "if": + Add(ParseIfStatement(toks)); + break; + case "else": + ParseElseStatement(toks, statements[statements.Count - 1]); + break; + case "choose": + Add(ParseChooseStatement(toks)); + break; + case "when": + Add(ParseWhenStatement(toks)); + break; + case "try": + Add(ParseTryStatement(toks)); + break; + case "catch": + ParseCatchStatement(toks, statements[statements.Count - 1]); + break; + case "throw": + Add(ParseThrowStatement(toks)); + break; default: if (IllegalKeywords.Contains(toks.First().Value)) - throw new Error(toks.First().SourceLine, "Statement '{0}' not supported in actors.", toks.First().Value); + throw new Error( + toks.First().SourceLine, + "Statement '{0}' not supported in actors.", + toks.First().Value + ); if (toks.Any(t => t.Value == "wait" || t.Value == "waitNext")) Add(ParseWaitStatement(toks)); else if (toks.First().Value == "state") Add(ParseStateDeclaration(toks)); else if (toks.First().Value == "switch" && toks.Any(t => t.Value == "return")) - throw new Error(toks.First().SourceLine, "Unsupported compound statement containing return."); + throw new Error( + toks.First().SourceLine, + "Unsupported compound statement containing return." + ); else if (toks.First().Value.StartsWith("#")) - throw new Error(toks.First().SourceLine, "Found \"{0}\". Preprocessor directives are not supported within ACTORs", toks.First().Value); + throw new Error( + toks.First().SourceLine, + "Found \"{0}\". Preprocessor directives are not supported within ACTORs", + toks.First().Value + ); else if (toks.RevSkipWhile(t => t.Value == ";").Any(NonWhitespace)) - Add(new PlainOldCodeStatement - { - code = str(NormalizeWhitespace(toks.RevSkipWhile(t => t.Value == ";"))) + ";" - }); + Add( + new PlainOldCodeStatement + { + code = + str(NormalizeWhitespace(toks.RevSkipWhile(t => t.Value == ";"))) + + ";" + } + ); break; - }; + } + ; } Statement ParseCompoundStatement(TokenRange toks) { var first = toks.First(NonWhitespace); - if (first.Value == "{") { + if (first.Value == "{") + { var inBraces = first.GetMatchingRangeIn(toks); if (!range(inBraces.End, toks.End).Consume("}").All(Whitespace)) - throw new Error(inBraces.Last().SourceLine, "Unexpected tokens after compound statement"); + throw new Error( + inBraces.Last().SourceLine, + "Unexpected tokens after compound statement" + ); return ParseCodeBlock(inBraces); - } else { + } + else + { List statements = new List(); - ParseStatement( toks.Skip(1), statements ); + ParseStatement(toks.Skip(1), statements); return statements[0]; } } @@ -981,7 +1252,8 @@ List ParseDescrCodeBlock(TokenRange toks) break; int pos = delim.Position + 1; - var potentialComment = range(pos, toks.End).SkipWhile(t => t.Value == "\t" || t.Value == " "); + var potentialComment = range(pos, toks.End) + .SkipWhile(t => t.Value == "\t" || t.Value == " "); if (!potentialComment.IsEmpty && potentialComment.First().Value.StartsWith("//")) { pos = potentialComment.First().Position + 1; @@ -992,7 +1264,10 @@ List ParseDescrCodeBlock(TokenRange toks) toks = range(pos, toks.End); } if (!toks.All(Whitespace)) - throw new Error(toks.First(NonWhitespace).SourceLine, "Trailing unterminated statement in code block"); + throw new Error( + toks.First(NonWhitespace).SourceLine, + "Trailing unterminated statement in code block" + ); return declarations; } @@ -1001,19 +1276,21 @@ CodeBlock ParseCodeBlock(TokenRange toks) List statements = new List(); while (true) { - Token delim = toks - .FirstOrDefault( - t=> t.ParenDepth == toks.First().ParenDepth && - t.BraceDepth == toks.First().BraceDepth && - (t.Value==";" || t.Value == "}") - ); + Token delim = toks.FirstOrDefault(t => + t.ParenDepth == toks.First().ParenDepth + && t.BraceDepth == toks.First().BraceDepth + && (t.Value == ";" || t.Value == "}") + ); if (delim == null) break; ParseStatement(range(toks.Begin, delim.Position + 1), statements); toks = range(delim.Position + 1, toks.End); } if (!toks.All(Whitespace)) - throw new Error(toks.First(NonWhitespace).SourceLine, "Trailing unterminated statement in code block"); + throw new Error( + toks.First(NonWhitespace).SourceLine, + "Trailing unterminated statement in code block" + ); return new CodeBlock { statements = statements.ToArray() }; } @@ -1028,7 +1305,7 @@ Descr ParseDescr(int pos, out int end) var toks = range(pos + 1, tokens.Length); var heading = toks.TakeWhile(t => t.Value != "{"); var body = range(heading.End + 1, tokens.Length) - .TakeWhile(t => t.BraceDepth > toks.First().BraceDepth || t.Value == ";" ); //assumes no whitespace between the last "}" and the ";" + .TakeWhile(t => t.BraceDepth > toks.First().BraceDepth || t.Value == ";"); //assumes no whitespace between the last "}" and the ";" ParseDescrHeading(descr, heading); descr.body = ParseDescrCodeBlock(body); @@ -1037,32 +1314,40 @@ Descr ParseDescr(int pos, out int end) return descr; } - Actor ParseActor( int pos, out int end ) { + Actor ParseActor(int pos, out int end) + { var actor = new Actor(); var head_token = tokens[pos]; actor.SourceLine = head_token.SourceLine; - var toks = range(pos+1, tokens.Length); + var toks = range(pos + 1, tokens.Length); var heading = toks.TakeWhile(t => t.Value != "{"); var toSemicolon = toks.TakeWhile(t => t.Value != ";"); actor.isForwardDeclaration = toSemicolon.Length < heading.Length; - if (actor.isForwardDeclaration) { + if (actor.isForwardDeclaration) + { heading = toSemicolon; - if (head_token.Value == "ACTOR" || head_token.Value == "SWIFT_ACTOR") { + if (head_token.Value == "ACTOR") + { ParseActorHeading(actor, heading); - } else { + } + else + { head_token.Assert("ACTOR expected!", t => false); } end = heading.End + 1; - } else { - var body = range(heading.End+1, tokens.Length) + } + else + { + var body = range(heading.End + 1, tokens.Length) .TakeWhile(t => t.BraceDepth > toks.First().BraceDepth); if (head_token.Value == "ACTOR" || head_token.Value == "SWIFT_ACTOR") { ParseActorHeading(actor, heading); } - else if (head_token.Value == "TEST_CASE") { + else if (head_token.Value == "TEST_CASE") + { ParseTestCaseHeading(actor, heading); actor.isTestCase = true; } @@ -1083,39 +1368,64 @@ string str(IEnumerable tokens) { return string.Join("", tokens.Select(x => x.Value).ToArray()); } + string str(int begin, int end) { - return str(range(begin,end)); + return str(range(begin, end)); } void CountParens() { - int BraceDepth = 0, ParenDepth = 0, LineCount = 1; - Token lastParen = null, lastBrace = null; + int BraceDepth = 0, + ParenDepth = 0, + LineCount = 1; + Token lastParen = null, + lastBrace = null; for (int i = 0; i < tokens.Length; i++) { switch (tokens[i].Value) { - case "}": BraceDepth--; break; - case ")": ParenDepth--; break; - case "\r\n": LineCount++; break; - case "\n": LineCount++; break; + case "}": + BraceDepth--; + break; + case ")": + ParenDepth--; + break; + case "\r\n": + LineCount++; + break; + case "\n": + LineCount++; + break; } - if (BraceDepth < 0) throw new Error(LineCount, "Mismatched braces"); - if (ParenDepth < 0) throw new Error(LineCount, "Mismatched parenthesis"); + if (BraceDepth < 0) + throw new Error(LineCount, "Mismatched braces"); + if (ParenDepth < 0) + throw new Error(LineCount, "Mismatched parenthesis"); tokens[i].Position = i; tokens[i].SourceLine = LineCount; tokens[i].BraceDepth = BraceDepth; tokens[i].ParenDepth = ParenDepth; - if (tokens[i].Value.StartsWith("/*")) LineCount += tokens[i].Value.Count(c=>c=='\n'); + if (tokens[i].Value.StartsWith("/*")) + LineCount += tokens[i].Value.Count(c => c == '\n'); switch (tokens[i].Value) { - case "{": BraceDepth++; if (BraceDepth==1) lastBrace = tokens[i]; break; - case "(": ParenDepth++; if (ParenDepth==1) lastParen = tokens[i]; break; + case "{": + BraceDepth++; + if (BraceDepth == 1) + lastBrace = tokens[i]; + break; + case "(": + ParenDepth++; + if (ParenDepth == 1) + lastParen = tokens[i]; + break; } } - if (BraceDepth != 0) throw new Error(lastBrace.SourceLine, "Unmatched brace"); - if (ParenDepth != 0) throw new Error(lastParen.SourceLine, "Unmatched parenthesis"); + if (BraceDepth != 0) + throw new Error(lastBrace.SourceLine, "Unmatched brace"); + if (ParenDepth != 0) + throw new Error(lastParen.SourceLine, "Unmatched parenthesis"); } void showTokens() @@ -1131,27 +1441,35 @@ void showTokens() } } - readonly Regex identifierPattern = new Regex(@"\G[a-zA-Z_][a-zA-Z_0-9]*", RegexOptions.Singleline); - - readonly Regex[] tokenExpressions = (new string[] { - @"\{", - @"\}", - @"\(", - @"\)", - @"\[", - @"\]", - @"//[^\n]*", - @"/[*]([*][^/]|[^*])*[*]/", - @"'(\\.|[^\'\n])*'", //< SOMEDAY: Not fully restrictive - @"""(\\.|[^\""\n])*""", - @"[a-zA-Z_][a-zA-Z_0-9]*", - @"\r\n", - @"\n", - @"::", - @":", - @"#[a-z]*", // Recognize preprocessor directives so that we can reject them - @".", - }).Select( x=>new Regex(@"\G"+x, RegexOptions.Singleline) ).ToArray(); + readonly Regex identifierPattern = new Regex( + @"\G[a-zA-Z_][a-zA-Z_0-9]*", + RegexOptions.Singleline + ); + + readonly Regex[] tokenExpressions = ( + new string[] + { + @"\{", + @"\}", + @"\(", + @"\)", + @"\[", + @"\]", + @"//[^\n]*", + @"/[*]([*][^/]|[^*])*[*]/", + @"'(\\.|[^\'\n])*'", //< SOMEDAY: Not fully restrictive + @"""(\\.|[^\""\n])*""", + @"[a-zA-Z_][a-zA-Z_0-9]*", + @"\r\n", + @"\n", + @"::", + @":", + @"#[a-z]*", // Recognize preprocessor directives so that we can reject them + @".", + } + ) + .Select(x => new Regex(@"\G" + x, RegexOptions.Singleline)) + .ToArray(); IEnumerable Tokenize(string text) { @@ -1171,7 +1489,7 @@ IEnumerable Tokenize(string text) } } if (!ok) - throw new Exception( String.Format("Can't tokenize! {0}", pos)); + throw new Exception(String.Format("Can't tokenize! {0}", pos)); } } } diff --git a/flow/actorcompiler/ParseTree.cs b/flow/actorcompiler/ParseTree.cs index 766d56b304c..f09ef74d66d 100644 --- a/flow/actorcompiler/ParseTree.cs +++ b/flow/actorcompiler/ParseTree.cs @@ -18,7 +18,7 @@ * limitations under the License. */ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -37,137 +37,174 @@ class VarDeclaration abstract class Statement { public int FirstSourceLine; + public virtual bool containsWait() { return false; } }; + class PlainOldCodeStatement : Statement { public string code; + public override string ToString() { return code; } }; + class StateDeclarationStatement : Statement { public VarDeclaration decl; + public override string ToString() { if (decl.initializerConstructorSyntax) return string.Format("State {0} {1}({2});", decl.type, decl.name, decl.initializer); else - return string.Format("State {0} {1} = {2};", decl.type, decl.name, decl.initializer); + return string.Format( + "State {0} {1} = {2};", + decl.type, + decl.name, + decl.initializer + ); } }; + class WhileStatement : Statement { public string expression; public Statement body; + public override bool containsWait() { return body.containsWait(); } }; + class ForStatement : Statement { public string initExpression = ""; public string condExpression = ""; public string nextExpression = ""; public Statement body; + public override bool containsWait() { return body.containsWait(); } }; + class RangeForStatement : Statement { public string rangeExpression; public string rangeDecl; public Statement body; + public override bool containsWait() { return body.containsWait(); } }; + class LoopStatement : Statement { public Statement body; + public override string ToString() { return "Loop " + body.ToString(); } + public override bool containsWait() { return body.containsWait(); } }; - class BreakStatement : Statement - { - }; - class ContinueStatement : Statement - { - }; + + class BreakStatement : Statement { }; + + class ContinueStatement : Statement { }; + class IfStatement : Statement { public string expression; public bool constexpr; public Statement ifBody; - public Statement elseBody; // might be null + public Statement elseBody; // might be null + public override bool containsWait() { return ifBody.containsWait() || (elseBody != null && elseBody.containsWait()); } }; + class ReturnStatement : Statement { public string expression; + public override string ToString() { return "Return " + expression; } }; + class WaitStatement : Statement { public VarDeclaration result; public string futureExpression; public bool resultIsState; public bool isWaitNext; + public override string ToString() { - return string.Format("Wait {0} {1} <- {2} ({3})", result.type, result.name, futureExpression, resultIsState ? "state" : "local"); + return string.Format( + "Wait {0} {1} <- {2} ({3})", + result.type, + result.name, + futureExpression, + resultIsState ? "state" : "local" + ); } + public override bool containsWait() { return true; } }; + class ChooseStatement : Statement { public Statement body; + public override string ToString() { return "Choose " + body.ToString(); } + public override bool containsWait() { return body.containsWait(); } }; + class WhenStatement : Statement { public WaitStatement wait; public Statement body; + public override string ToString() { return string.Format("When ({0}) {1}", wait, body); } + public override bool containsWait() { return true; } }; + class TryStatement : Statement { public struct Catch @@ -179,6 +216,7 @@ public struct Catch public Statement tryBody; public List catches; + public override bool containsWait() { if (tryBody.containsWait()) @@ -189,6 +227,7 @@ public override bool containsWait() return false; } }; + class ThrowStatement : Statement { public string expression; @@ -197,14 +236,18 @@ class ThrowStatement : Statement class CodeBlock : Statement { public Statement[] statements; + public override string ToString() { - return string.Join("\n", + return string.Join( + "\n", new string[] { "CodeBlock" } .Concat(statements.Select(s => s.ToString())) .Concat(new string[] { "EndCodeBlock" }) - .ToArray()); + .ToArray() + ); } + public override bool containsWait() { foreach (Statement s in statements) @@ -214,7 +257,6 @@ public override bool containsWait() } }; - class Declaration { public string type; @@ -229,7 +271,7 @@ class Actor public string name; public string enclosingClass = null; public VarDeclaration[] parameters; - public VarDeclaration[] templateFormals; //< null if not a template + public VarDeclaration[] templateFormals; //< null if not a template public CodeBlock body; public int SourceLine; public bool isStatic = false; @@ -239,8 +281,15 @@ class Actor public bool isForwardDeclaration = false; public bool isTestCase = false; - public bool IsCancellable() { return returnType != null && !isUncancellable; } - public void SetUncancellable() { isUncancellable = true; } + public bool IsCancellable() + { + return returnType != null && !isUncancellable; + } + + public void SetUncancellable() + { + isUncancellable = true; + } }; class Descr diff --git a/flow/actorcompiler/Program.cs b/flow/actorcompiler/Program.cs index 742dd21a24f..8072575fe3d 100644 --- a/flow/actorcompiler/Program.cs +++ b/flow/actorcompiler/Program.cs @@ -25,7 +25,8 @@ namespace actorcompiler { class Program { - private static void OverwriteByMove(string target, string temporaryFile) { + private static void OverwriteByMove(string target, string temporaryFile) + { if (File.Exists(target)) { File.SetAttributes(target, FileAttributes.Normal); @@ -41,17 +42,27 @@ public static int Main(string[] args) if (args.Length < 2) { Console.WriteLine("Usage:"); - Console.WriteLine(" actorcompiler [--disable-diagnostics] [--generate-probes]"); + Console.WriteLine( + " actorcompiler [--disable-diagnostics] [--generate-probes]" + ); return 100; } Console.WriteLine("actorcompiler {0}", string.Join(" ", args)); - string input = args[0], output = args[1], outputtmp = args[1] + ".tmp", outputUid = args[1] + ".uid"; + string input = args[0], + output = args[1], + outputtmp = args[1] + ".tmp", + outputUid = args[1] + ".uid"; ErrorMessagePolicy errorMessagePolicy = new ErrorMessagePolicy(); - foreach (var arg in args) { - if (arg.StartsWith("--")) { - if (arg.Equals("--disable-diagnostics")) { + foreach (var arg in args) + { + if (arg.StartsWith("--")) + { + if (arg.Equals("--disable-diagnostics")) + { errorMessagePolicy.DisableDiagnostics = true; - } else if (arg.Equals("--generate-probes")) { + } + else if (arg.Equals("--generate-probes")) + { generateProbes = true; } } @@ -59,16 +70,29 @@ public static int Main(string[] args) try { var inputData = File.ReadAllText(input); - var parser = new ActorParser(inputData, input.Replace('\\', '/'), errorMessagePolicy, generateProbes); + var parser = new ActorParser( + inputData, + input.Replace('\\', '/'), + errorMessagePolicy, + generateProbes + ); - using (var outputStream = new StreamWriter(outputtmp)) { + using (var outputStream = new StreamWriter(outputtmp)) + { parser.Write(outputStream, output.Replace('\\', '/')); } OverwriteByMove(output, outputtmp); - using (var outputStream = new StreamWriter(outputtmp)) { - foreach(var entry in parser.uidObjects) { - outputStream.WriteLine("{0}|{1}|{2}", entry.Key.Item1, entry.Key.Item2, entry.Value); + using (var outputStream = new StreamWriter(outputtmp)) + { + foreach (var entry in parser.uidObjects) + { + outputStream.WriteLine( + "{0}|{1}|{2}", + entry.Key.Item1, + entry.Key.Item2, + entry.Value + ); } } OverwriteByMove(outputUid, outputtmp); @@ -77,7 +101,12 @@ public static int Main(string[] args) } catch (actorcompiler.Error e) { - Console.Error.WriteLine("{0}({1}): error FAC1000: {2}", input, e.SourceLine, e.Message); + Console.Error.WriteLine( + "{0}({1}): error FAC1000: {2}", + input, + e.SourceLine, + e.Message + ); if (File.Exists(outputtmp)) File.Delete(outputtmp); if (File.Exists(output)) @@ -89,7 +118,12 @@ public static int Main(string[] args) } catch (Exception e) { - Console.Error.WriteLine("{0}({1}): error FAC2000: Internal {2}", input, 1, e.ToString()); + Console.Error.WriteLine( + "{0}({1}): error FAC2000: Internal {2}", + input, + 1, + e.ToString() + ); if (File.Exists(outputtmp)) File.Delete(outputtmp); if (File.Exists(output)) From 694dd4d540be5e6f497f1aac6556e27bdc949b97 Mon Sep 17 00:00:00 2001 From: Xiaoge Su Date: Sun, 18 Aug 2024 13:36:42 -0700 Subject: [PATCH 2/3] Scratch of ActorMonitor --- cmake/AM.cmake | 20 ++++ flow/ActorUID.cpp | 27 +++++ flow/GUID.cpp | 63 +++++++++++ flow/actorcompiler/uid_collector.py | 165 ++++++++++++++++++++++++++++ flow/include/flow/ActorUID.h | 44 ++++++++ flow/include/flow/GUID.h | 59 ++++++++++ 6 files changed, 378 insertions(+) create mode 100644 cmake/AM.cmake create mode 100644 flow/ActorUID.cpp create mode 100644 flow/GUID.cpp create mode 100644 flow/actorcompiler/uid_collector.py create mode 100644 flow/include/flow/ActorUID.h create mode 100644 flow/include/flow/GUID.h diff --git a/cmake/AM.cmake b/cmake/AM.cmake new file mode 100644 index 00000000000..2ffb06175ba --- /dev/null +++ b/cmake/AM.cmake @@ -0,0 +1,20 @@ +# Actor Monitor + +# Advanced Materials is a peer-reviewed journal covering material topics. Its impact factor is 29.4(2022) + +set(ACTOR_MONITORING DISABLED CACHE STRING "Actor monitor") +set_property(CACHE ACTOR_MONITORING PROPERTY STRINGS DISABLED MINIMAL FULL) + +if ((FDB_RELEASE OR FDB_RELEASE_CANDIDATE) AND NOT (ACTOR_MONITORING STREQUAL "DISABLED")) + message(FATAL_ERROR "AM will cause more than 10% slowdown and should not be used in release") +endif () + +if (ACTOR_MONITORING STREQUAL "DISABLED") + add_compile_definitions(-DACTOR_MONITORING=0) +elseif (ACTOR_MONITORING STREQUAL "MINIMAL") + add_compile_definitions(-DACTOR_MONITORING=1) +elseif (ACTOR_MONITORING STREQUAL "FULL") + add_compile_definitions(-DACTOR_MONITORING=2) +endif () + +message(STATUS "ACTOR monitoring level is ${ACTOR_MONITORING}") diff --git a/flow/ActorUID.cpp b/flow/ActorUID.cpp new file mode 100644 index 00000000000..5281843d8c2 --- /dev/null +++ b/flow/ActorUID.cpp @@ -0,0 +1,27 @@ +/* + * FoundationDB ACTOR UID data + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ + +#include "flow/ActorUID.h" + +#define __ACTOR_UID_DATA_H_INCLUDE +// The data is generated after all actors are compiled +#include "flow/ActorUIDData.h" +#undef __ACTOR_UID_DATA_H_INCLUDE \ No newline at end of file diff --git a/flow/GUID.cpp b/flow/GUID.cpp new file mode 100644 index 00000000000..9ec60ebf748 --- /dev/null +++ b/flow/GUID.cpp @@ -0,0 +1,63 @@ +/* + * GUID.cpp + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flow/GUID.h" + +#include +#include +#include + +std::string GUID::toString() const { + std::stringstream ss; + ss << std::setfill('0') << std::setw(sizeof(uint64_t) * 2) << first(); + ss << std::setfill('0') << std::setw(sizeof(uint64_t) * 2) << second(); + return ss.str(); +} + +namespace details { +// The following hash-combination algorithm is copied from +// https://stackoverflow.com/a/50978188/178732 + +template +T xorshift(const T& n, int i) { + return n ^ (n >> i); +} + +// a hash function with another name as to not confuse with std::hash +uint64_t distribute(const uint64_t& n) { + static constexpr uint64_t p = 0x5555555555555555ull; // pattern of alternating 0 and 1 + static constexpr uint64_t c = 17316035218449499591ull; // random uneven integer constant; + return c * xorshift(p * xorshift(n, 32), 32); +} + +// call this function with the old seed and the new key to be hashed and combined into the new seed value, respectively +// the final hash +template +inline size_t hash_combine(std::size_t seed, const T& v) { + return std::rotl(seed, std::numeric_limits::digits / 3) ^ distribute(std::hash{}(v)); +} + +} // namespace details + +namespace std { +size_t hash::operator()(const GUID& guid) const { + return ::details::hash_combine(hash{}(guid.first()), guid.second()); +} +} // namespace std diff --git a/flow/actorcompiler/uid_collector.py b/flow/actorcompiler/uid_collector.py new file mode 100644 index 00000000000..87595ad205d --- /dev/null +++ b/flow/actorcompiler/uid_collector.py @@ -0,0 +1,165 @@ +#! /usr/bin/env python3 + +import dataclasses +import glob +import io +import logging +import os.path +import pathlib +import secrets +import sys + +from typing import List, Tuple + +logger = logging.getLogger("uid_collector") + + +LICENSE: str = r"""/* + * FoundationDB ACTOR UID data + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Do not include this file directly. + */ +""" + + +@dataclasses.dataclass +class Identifier: + guid1: int + guid2: int + path: pathlib.Path + line: int + type_: str + name: str + + def parse(line: str) -> "Identifier": + nsplit = line.strip().split("|") + assert len(nsplit) == 6, "Expecting 6 terms" + + guid1_s, guid2_s, path, line_s, type_, name = nsplit + + return Identifier(int(guid1_s), int(guid2_s), path, int(line_s), type_, name) + + +def parse(stream: io.TextIOWrapper) -> List[Identifier]: + return [Identifier.parse(line) for line in stream.readlines()] + + +def collect(path: pathlib.Path) -> List[Identifier]: + result = [] + + for item in glob.glob("**/*.uid", root_dir=path, recursive=True): + logger.info(f"Parsing {path}...") + full_path = os.path.join(path, item) + with open(full_path, "r") as stream: + r = parse(stream) + logger.info(f"Found {len(r)} items") + result.extend(r) + + return result + + +def generate_binary_version() -> Tuple[int, int]: + """Generates two 64-bit random numbers as the unique binary verison of fdbserver. + The ACTOR identifiers/block identifiers mapping are versioned using this. + """ + return ( + int.from_bytes(secrets.token_bytes(8)), + int.from_bytes(secrets.token_bytes(8)), + ) + + +def render( + stream: io.TextIOWrapper, binary_version: Tuple[int, int], items: List[Identifier] +): + stream.write(LICENSE) + stream.write( + r""" +#ifndef __ACTOR_UID_DATA_H_INCLUDE +#error This file should be included by ActorUID.cpp only +#endif + +#include "flow/ActorUID.h" + +#include + +namespace ActorMonitoring { +""" + ) + stream.write( + f""" +const GUID BINARY_GUID = GUID({binary_version[0]}ULL, {binary_version[1]}ULL); +""" + ) + stream.write( + r""" +namespace { + +std::unordered_map initializeActorDebuggingData() { +#if ACTOR_MONITORING == ACTOR_MONITORING_DISABLED + return {}; +#else // ACTOR_MONITORING == ACTOR_MONITORING_DISABLED + // FIXME: For unknown reasons string literals ""sv is not working with error + // no matching literal operator for call to 'operator""sv' with arguments of types 'const char *' and 'unsigned long', and no matching literal operator template + return { +""" + ) + + for item in items: + # FIXME: Escape path for item.path in string + template = ' < <{0}ULL, {1}ULL>, >,\n'.format( + item.guid1, item.guid2, item.path, item.line, item.name, item.type_ + ) + stream.write(template.replace("<", "{").replace(">", "}")) + + stream.write( + r""" + }; // return +#endif // ACTOR_MONITORING == ACTOR_MONITORING_DISABLED +} // initializeActorDebuggingData + +} // namespace + +const std::unordered_map ACTOR_DEBUGGING_DATA = initializeActorDebuggingData(); + +} // namespace ActorMonitoring +""" + ) + + +def main(argv: List[str]): + argc = len(argv) + logging.basicConfig(level=logging.WARN) + + if argc not in [2, 3]: + print( + "uid_collector.py [FoundationDB build path] [Output File]", file=sys.stderr + ) + sys.exit(-1) + + stream = None + if argc == 2: + stream = sys.stdout + render(sys.stdout, generate_binary_version(), collect(os.path.abspath(argv[1]))) + if argc == 3: + stream = open(argv[2], "w") + + render(stream, generate_binary_version(), collect(os.path.abspath(argv[1]))) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/flow/include/flow/ActorUID.h b/flow/include/flow/ActorUID.h new file mode 100644 index 00000000000..9fec69addfa --- /dev/null +++ b/flow/include/flow/ActorUID.h @@ -0,0 +1,44 @@ +/* + * ActorUID.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOW_ACTOR_UID_H +#define FLOW_ACTOR_UID_H +#pragma once + +#include "flow/ActorContext.h" + +#include +#include + +namespace ActorMonitoring { +struct ActorDebuggingData { + std::string_view path; + uint32_t lineNumber; + std::string_view actorName; + // TODO Use an enum? + std::string_view type; +}; + +extern const GUID BINARY_GUID; +extern const std::unordered_map ACTOR_DEBUGGING_DATA; + +} // ActorMonitoring + +#endif // FLOW_ACTOR_UID_H \ No newline at end of file diff --git a/flow/include/flow/GUID.h b/flow/include/flow/GUID.h new file mode 100644 index 00000000000..f9a95255212 --- /dev/null +++ b/flow/include/flow/GUID.h @@ -0,0 +1,59 @@ +/* + * GUID.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2023 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOW_GUID_H +#define FLOW_GUID_H +#pragma once + +#include +#include +#include + +// GUID is a simple implementation of globally unique identifier (see .NET Guid Struct) +class GUID { + uint64_t p1; + uint64_t p2; + +public: + constexpr GUID() : p1(0ULL), p2(0ULL) {} + constexpr GUID(const uint64_t p1_, const uint64_t p2_) : p1(p1_), p2(p2_) {} + + bool operator==(const GUID& other) const noexcept { return p1 == other.p1 && p2 == other.p2; } + bool operator!=(const GUID& other) const noexcept { return !(*this == other); } + + uint64_t first() const noexcept { return p1; } + uint64_t second() const noexcept { return p2; } + + std::string toString() const; + + template + void serialize(Ar& ar) { + serializer(ar, p1, p2); + } +}; + +namespace std { +template <> +struct hash { + size_t operator()(const GUID& guid) const; +}; +} // namespace std + +#endif // FLOW_GUID_H \ No newline at end of file From a53cbbf72b07566021b031f2dd6f65896035348a Mon Sep 17 00:00:00 2001 From: Xiaoge Su Date: Sun, 18 Aug 2024 13:37:43 -0700 Subject: [PATCH 3/3] Scratch of ActorMonitor II --- CMakeLists.txt | 15 +- flow/ActorContext.cpp | 228 ++++++++++++++-------------- flow/CMakeLists.txt | 224 ++++++++++++++++----------- flow/acac.cpp | 77 +++++++--- flow/actorcompiler/ActorCompiler.cs | 137 +++++++++++++---- flow/actorcompiler/ActorParser.cs | 8 +- flow/actorcompiler/Program.cs | 15 +- flow/include/flow/ActorContext.h | 126 ++++++++++----- flow/include/flow/flow.h | 1 + 9 files changed, 525 insertions(+), 306 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9a866b74ec..b83ee12aff0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,8 @@ project(foundationdb list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") -message (STATUS "${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}") +message(STATUS "Source code directory: ${PROJECT_SOURCE_DIR}") +message(STATUS "Build directory: ${PROJECT_BINARY_DIR}") if("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}") message(FATAL_ERROR "In-source builds are forbidden") endif() @@ -53,15 +54,6 @@ endif() set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) -option(WITH_ACAC "Enable actor stack recording" OFF) -if (WITH_ACAC) - message(STATUS "Build FoundationDB with AcAC support") - if (FDB_RELEASE OR FDB_RELEASE_CANDIDATE) - message(FATAL_ERROR "ACAC will cause severe slowdown of the system and SHOULD not be enabled in Release.") - endif() - add_compile_definitions(WITH_ACAC) -endif() - ################################################################################ # Packages used for bindings ################################################################################ @@ -129,6 +121,9 @@ set(FDB_PREV3_RELEASE_VERSION "7.0.0") # Flow ################################################################################ +# Flags for Actor Monitoring +include(AM) + include(utils) # Flow and other tools are written in C# - so we need that dependency diff --git a/flow/ActorContext.cpp b/flow/ActorContext.cpp index f7332cef0f4..866c7c194a9 100644 --- a/flow/ActorContext.cpp +++ b/flow/ActorContext.cpp @@ -1,21 +1,51 @@ +/* + * ActorContext.cpp + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "flow/ActorContext.h" +#include "flow/ActorUID.h" -#ifdef WITH_ACAC +#if ACTOR_MONITORING != ACTOR_MONITORING_DISABLED #include #include -#include +#include +#include +#include -#include "flow/flow.h" #include "libb64/encode.h" #include "libb64/decode.h" +#include "flow/ActorUID.h" +#include "flow/flow.h" +#include "flow/GUID.h" + +namespace ActorMonitoring { + namespace { + std::vector g_currentExecutionContext; + std::unordered_map g_activeActors; -ActorID getActorID() { - static thread_local ActorID actorID = INIT_ACTOR_ID; +inline ActorID getActorID() { + static thread_local ActorID actorID = ActorMonitoring::INIT_ACTOR_ID; return ++actorID; } @@ -33,23 +63,38 @@ inline bool isActorOnMainThread() { // is never called, the N2::thread_network will always be nullptr. In this case, Sim2::isOnMainThread will always // return false and not reliable. if (g_network) [[likely]] { - return g_network->isSimulated() ? true : g_network->isOnMainThread(); + return g_network->isSimulated() || g_network->isOnMainThread(); } else { return false; } } +inline double gn_now() { + if (g_network == nullptr) [[unlikely]] { + return 0.0; + } + return g_network->now(); +} + } // anonymous namespace -using ActiveActorsCount_t = uint32_t; +ActorInfoMinimal::ActorInfoMinimal() : identifier(ActorIdentifier()), id(INVALID_ACTOR_ID), spawner(INVALID_ACTOR_ID) {} -ActiveActor::ActiveActor() : identifier(), id(), spawnTime(0.0), spawner(INVALID_ACTOR_ID) {} +ActorInfoMinimal::ActorInfoMinimal(const ActorIdentifier& identifier_, const ActorID id_, const ActorID spawner_) + : identifier(identifier_), id(id_), spawner(spawner_) {} + +ActorInfoFull::ActorInfoFull() + : ActorInfoMinimal(), spawnTime(-1), lastResumeTime(-1), lastYieldTime(-1), numResumes(0) {} + +ActorInfoFull::ActorInfoFull(const ActorIdentifier& identifier_, const ActorID id_, const ActorID spawner_) + : ActorInfoMinimal(identifier_, id_, spawner_), spawnTime(-1), lastResumeTime(-1), lastYieldTime(-1), numResumes(-1) { +} -ActiveActor::ActiveActor(const ActorIdentifier& identifier_, const ActorID& id_, const ActorID& spawnerID_) - : identifier(identifier_), id(id_), spawnTime(g_network != nullptr ? g_network->now() : 0.0), spawner(spawnerID_) {} +using ActiveActorsCount_t = uint32_t; ActiveActorHelper::ActiveActorHelper(const ActorIdentifier& actorIdentifier) { if (!isActorOnMainThread()) [[unlikely]] { + // Only capture ACTORs on the main thread return; } const auto actorID_ = getActorID(); @@ -71,6 +116,10 @@ ActorExecutionContextHelper::ActorExecutionContextHelper(const ActorID& actorID_ return; } g_currentExecutionContext.emplace_back(actorID_, blockIdentifier_); +#if ACTOR_MONITORING == ACTOR_MONITORING_FULL + g_activeActors[actorID_].lastResumeTime = gn_now(); + ++g_activeActors[actorID_].numResumes; +#endif } ActorExecutionContextHelper::~ActorExecutionContextHelper() { @@ -84,124 +133,79 @@ ActorExecutionContextHelper::~ActorExecutionContextHelper() { g_currentExecutionContext.pop_back(); } -// TODO: Rewrite this function for better display -void dumpActors(std::ostream& stream) { - stream << "Current active ACTORs:" << std::endl; - for (const auto& [actorID, activeActor] : g_activeActors) { - stream << std::setw(10) << actorID << " " << activeActor.identifier.toString() << std::endl; - if (activeActor.spawner != INVALID_ACTOR_ID) { - stream << " Spawn by " << std::setw(10) << activeActor.spawner << std::endl; - } +ActorYieldHelper::ActorYieldHelper(const ActorID& actorID_, const ActorBlockIdentifier& blockIdentifier_) { +#if ACTOR_MONITORING == ACTOR_MONITORING_FULL + if (!isActorOnMainThread()) [[unlikely]] { + return; } + g_activeActors[actorID_].lastYieldTime = gn_now(); + g_activeActors[actorID_].yieldBlockID = blockIdentifier_; +#endif } namespace { -std::vector getCallBacktraceOfActor(const ActorID& actorID) { - std::vector actorBacktrace; - auto currentActorID = actorID; - for (;;) { - if (currentActorID == INIT_ACTOR_ID) { - // Reaching the root - break; - } - if (g_activeActors.count(currentActorID) == 0) { - // TODO: Understand why this happens and react properly - break; - } - actorBacktrace.push_back(g_activeActors.at(currentActorID)); - if (g_activeActors.at(currentActorID).spawner != INVALID_ACTOR_ID) { - currentActorID = g_activeActors.at(currentActorID).spawner; - } else { - // TODO: Understand why the actor has no spawner ID - break; - } - } - return actorBacktrace; +void encodeBinaryGUID(BinaryWriter& writer) { + writer << BINARY_GUID; } -} // anonymous namespace - -void dumpActorCallBacktrace() { - std::string backtrace = encodeActorContext(ActorContextDumpType::CURRENT_CALL_BACKTRACE); - std::cout << backtrace << std::endl; -} +} // namespace std::string encodeActorContext(const ActorContextDumpType dumpType) { BinaryWriter writer(Unversioned()); - auto writeActorInfo = [&writer](const ActiveActor& actor) { - writer << actor.id << actor.identifier << actor.spawner; - }; - - writer << static_cast(dumpType) - << (g_currentExecutionContext.empty() ? INVALID_ACTOR_ID : g_currentExecutionContext.back().actorID); - - switch (dumpType) { - case ActorContextDumpType::FULL_CONTEXT: - writer << static_cast(g_activeActors.size()); - for (const auto& [actorID, activeActor] : g_activeActors) { - writeActorInfo(activeActor); - } - break; - case ActorContextDumpType::CURRENT_STACK: - // Only current call stack - { - if (g_currentExecutionContext.empty()) { - writer << static_cast(0); - break; - } - writer << static_cast(g_currentExecutionContext.size()); - for (const auto& context : g_currentExecutionContext) { - writeActorInfo(g_activeActors.at(context.actorID)); - } - } - break; - case ActorContextDumpType::CURRENT_CALL_BACKTRACE: - // The call backtrace of current active actor - { - if (g_currentExecutionContext.empty()) { - writer << static_cast(0); - break; - } - const auto actors = getCallBacktraceOfActor(g_currentExecutionContext.back().actorID); - writer << static_cast(actors.size()); - for (const auto& item : actors) { - writeActorInfo(item); - } - } - break; - default: - UNREACHABLE(); - } - const std::string data = writer.toValue().toString(); - return base64::encoder::from_string(data); + encodeBinaryGUID(writer); + + return ""; } DecodedActorContext decodeActorContext(const std::string& caller) { - DecodedActorContext result; - const auto decoded = base64::decoder::from_string(caller); - BinaryReader reader(decoded, Unversioned()); - - std::underlying_type_t dumpTypeRaw; - reader >> dumpTypeRaw; - result.dumpType = static_cast(dumpTypeRaw); - - reader >> result.currentRunningActor; - - ActiveActorsCount_t actorCount; - reader >> actorCount; - - std::unordered_map> actors; - for (ActiveActorsCount_t i = 0; i < actorCount; ++i) { - ActorID id; - ActorID spawner; - ActorIdentifier identifier; - reader >> id >> identifier >> spawner; - result.context.emplace_back(id, identifier, spawner); + return DecodedActorContext(); +} + +namespace { + +auto getActorInfoFromActorID(const ActorID& actorID) -> std::optional { + std::optional result; + + if (auto iter = g_activeActors.find(actorID); iter != std::end(g_activeActors)) { + result.emplace(iter->second); + } + + return result; +} + +auto getActorDebuggingDataFromIdentifier(const ActorIdentifier& actorIdentifier) -> std::optional { + std::optional result; + + if (auto iter = ACTOR_DEBUGGING_DATA.find(actorIdentifier); iter != std::end(ACTOR_DEBUGGING_DATA)) { + result.emplace(iter->second); } return result; } -#endif // WITH_ACAC +} // namespace + +void dumpActorCallBacktrace() { + std::cout << "Length of ACTOR stack: " << g_currentExecutionContext.size() << std::endl; + std::cout << "NumActors=" << g_activeActors.size() << std::endl; + std::cout << "NumDebugDatas=" << ACTOR_DEBUGGING_DATA.size() << std::endl; + for (const auto& block : g_currentExecutionContext) { + std::cout << std::setw(10) << block.actorID << "\t"; + if (const auto info = getActorInfoFromActorID(block.actorID); info.has_value()) { + if (const auto debugData = getActorDebuggingDataFromIdentifier(info->identifier); debugData.has_value()) { + std::cout << std::setw(30) << debugData->actorName << "\t" << debugData->path << ":" + << debugData->lineNumber << std::endl; + } else { + std::cout << "No debug data available" << std::endl; + } + } else { + std::cout << "No ACTOR info" << std::endl; + } + } +} + +} // namespace ActorMonitoring + +#endif diff --git a/flow/CMakeLists.txt b/flow/CMakeLists.txt index 2dff6b3c2e3..30167b84da2 100644 --- a/flow/CMakeLists.txt +++ b/flow/CMakeLists.txt @@ -4,10 +4,10 @@ option(FLOW_USE_ZSTD "Enable zstd compression in flow" OFF) fdb_find_sources(FLOW_SRCS) -if (FLOW_USE_ZSTD) +if(FLOW_USE_ZSTD) # NOTE: To enable boost::iostreams with zstd library support, manually add # zstd.cpp to source files is required. Ref: - # https://www.boost.org/doc/libs/1_79_0/libs/iostreams/doc/installation.html + # https://www.boost.org/doc/libs/1_79_0/libs/iostreams/doc/installation.html list(APPEND FLOW_SRCS ../contrib/boost_zstd/zstd.cpp) endif() @@ -18,61 +18,104 @@ list(REMOVE_ITEM FLOW_SRCS MkCertCli.cpp) list(REMOVE_ITEM FLOW_SRCS acac.cpp) if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") - list(APPEND FLOW_SRCS aarch64/memcmp.S aarch64/memcpy.S) + list(APPEND FLOW_SRCS aarch64/memcmp.S aarch64/memcpy.S) endif() make_directory(${CMAKE_CURRENT_BINARY_DIR}/include/flow) -set(FDB_API_VERSION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/ApiVersions.cmake" CACHE STRING "Api version cmake file." FORCE) +set(FDB_API_VERSION_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/ApiVersions.cmake" + CACHE STRING "Api version cmake file." FORCE) include(${FDB_API_VERSION_FILE}) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ApiVersion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/include/flow/ApiVersion.h) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ApiVersion.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/include/flow/ApiVersion.h) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/SourceVersion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/include/flow/SourceVersion.h) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/include/flow/config.h) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/SourceVersion.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/include/flow/SourceVersion.h) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/include/flow/config.h) set(PROTOCOL_VERSION_PYTHON_EXECUTABLE "${Python3_EXECUTABLE}") find_package(Python3 REQUIRED COMPONENTS Interpreter) find_package(Jinja2) if(NOT Jinja2_FOUND) - message(STATUS "Jinja2 not found, setting up virtual environment for protocol_version.py") + message( + STATUS + "Jinja2 not found, setting up virtual environment for protocol_version.py" + ) set(PROTOCOL_VERSION_VENV_DIR "${CMAKE_BINARY_DIR}/protocol_version-venv") - execute_process(COMMAND "${Python3_EXECUTABLE}" -m venv ${PROTOCOL_VERSION_VENV_DIR}) + execute_process(COMMAND "${Python3_EXECUTABLE}" -m venv + ${PROTOCOL_VERSION_VENV_DIR}) find_program( VENV_Python3_EXECUTABLE NAMES python3 python3.exe - PATHS ${PROTOCOL_VERSION_VENV_DIR}/Scripts ${PROTOCOL_VERSION_VENV_DIR}/bin REQUIRED + PATHS ${PROTOCOL_VERSION_VENV_DIR}/Scripts ${PROTOCOL_VERSION_VENV_DIR}/bin + REQUIRED NO_DEFAULT_PATH NO_CACHE DOC "Checking Python3 executable in virtual environment") - execute_process(COMMAND "${VENV_Python3_EXECUTABLE}" -m ensurepip COMMAND_ERROR_IS_FATAL ANY) - execute_process(COMMAND "${VENV_Python3_EXECUTABLE}" -m pip install --upgrade pip COMMAND_ERROR_IS_FATAL ANY) - execute_process(COMMAND "${VENV_Python3_EXECUTABLE}" -m pip install -r "${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/requirements.txt" COMMAND_ERROR_IS_FATAL ANY) + execute_process(COMMAND "${VENV_Python3_EXECUTABLE}" -m ensurepip + COMMAND_ERROR_IS_FATAL ANY) + execute_process(COMMAND "${VENV_Python3_EXECUTABLE}" -m pip install --upgrade + pip COMMAND_ERROR_IS_FATAL ANY) + execute_process( + COMMAND + "${VENV_Python3_EXECUTABLE}" -m pip install -r + "${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/requirements.txt" + COMMAND_ERROR_IS_FATAL ANY) set(PROTOCOL_VERSION_PYTHON_EXECUTABLE "${VENV_Python3_EXECUTABLE}") endif() -set(FDB_PROTOCOL_VERSION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/ProtocolVersions.cmake" CACHE STRING "Protocol version cmake file." FORCE) -set(FDB_PROTOCOL_VERSION_HEADER_FILE "${CMAKE_CURRENT_BINARY_DIR}/include/flow/ProtocolVersion.h") -set(FDB_PROTOCOL_VERSION_JAVA_FILE "${CMAKE_CURRENT_BINARY_DIR}/include/flow/ProtocolVersion.java") -set(FDB_PROTOCOL_VERSION_PYTHON_FILE "${CMAKE_CURRENT_BINARY_DIR}/include/flow/protocol_version.py") +set(FDB_PROTOCOL_VERSION_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/ProtocolVersions.cmake" + CACHE STRING "Protocol version cmake file." FORCE) +set(FDB_PROTOCOL_VERSION_HEADER_FILE + "${CMAKE_CURRENT_BINARY_DIR}/include/flow/ProtocolVersion.h") +set(FDB_PROTOCOL_VERSION_JAVA_FILE + "${CMAKE_CURRENT_BINARY_DIR}/include/flow/ProtocolVersion.java") +set(FDB_PROTOCOL_VERSION_PYTHON_FILE + "${CMAKE_CURRENT_BINARY_DIR}/include/flow/protocol_version.py") add_custom_command( - OUTPUT ${FDB_PROTOCOL_VERSION_HEADER_FILE} - COMMAND ${PROTOCOL_VERSION_PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/protocol_version.py --source ${FDB_PROTOCOL_VERSION_FILE} --generator cpp --output ${FDB_PROTOCOL_VERSION_HEADER_FILE} - COMMAND ${PROTOCOL_VERSION_PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/protocol_version.py --source ${FDB_PROTOCOL_VERSION_FILE} --generator java --output ${FDB_PROTOCOL_VERSION_JAVA_FILE} - COMMAND ${PROTOCOL_VERSION_PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/protocol_version.py --source ${FDB_PROTOCOL_VERSION_FILE} --generator python --output ${FDB_PROTOCOL_VERSION_PYTHON_FILE} - DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/protocol_version.py - ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/ProtocolVersion.h.template - ${CMAKE_CURRENT_SOURCE_DIR}/ProtocolVersions.cmake -) + OUTPUT ${FDB_PROTOCOL_VERSION_HEADER_FILE} + COMMAND + ${PROTOCOL_VERSION_PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/protocol_version.py --source + ${FDB_PROTOCOL_VERSION_FILE} --generator cpp --output + ${FDB_PROTOCOL_VERSION_HEADER_FILE} + COMMAND + ${PROTOCOL_VERSION_PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/protocol_version.py --source + ${FDB_PROTOCOL_VERSION_FILE} --generator java --output + ${FDB_PROTOCOL_VERSION_JAVA_FILE} + COMMAND + ${PROTOCOL_VERSION_PYTHON_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/protocol_version.py --source + ${FDB_PROTOCOL_VERSION_FILE} --generator python --output + ${FDB_PROTOCOL_VERSION_PYTHON_FILE} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/protocol_version.py + ${CMAKE_CURRENT_SOURCE_DIR}/protocolversion/ProtocolVersion.h.template + ${CMAKE_CURRENT_SOURCE_DIR}/ProtocolVersions.cmake) add_custom_target(ProtocolVersion DEPENDS ${FDB_PROTOCOL_VERSION_HEADER_FILE}) +set(UID_COLLECTOR_PY_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/actorcompiler/uid_collector.py") +set(ACTOR_UID_CPP_DATA_PATH + "${CMAKE_CURRENT_BINARY_DIR}/include/flow/ActorUIDData.h") +add_custom_command( + OUTPUT "${ACTOR_UID_CPP_DATA_PATH}" + COMMAND ${Python3_EXECUTABLE} "${UID_COLLECTOR_PY_PATH}" + "${CMAKE_BINARY_DIR}" "${ACTOR_UID_CPP_DATA_PATH}" + DEPENDS "${UID_COLLECTOR_PY_PATH}" fdbrpc_actors fdbclient_actors + fdbserver_actors flow_actors) +add_custom_target(ActorUID_CPP DEPENDS "${ACTOR_UID_CPP_DATA_PATH}") + add_flow_target(STATIC_LIBRARY NAME flow SRCS ${FLOW_SRCS}) add_flow_target(STATIC_LIBRARY NAME flow_sampling SRCS ${FLOW_SRCS}) -add_dependencies(flow ProtocolVersion) -add_dependencies(flow_sampling ProtocolVersion) +add_dependencies(flow ProtocolVersion ActorUID_CPP) +add_dependencies(flow_sampling ProtocolVersion ActorUID_CPP) -if (FLOW_USE_ZSTD) +if(FLOW_USE_ZSTD) include(CompileZstd) compile_zstd() @@ -82,89 +125,90 @@ endif() # When creating a static or shared library, undefined symbols will be ignored. # Since we want to ensure no symbols from other modules are used, create an -# executable so the linker will throw errors if it can't find the declaration -# of a symbol. +# executable so the linker will throw errors if it can't find the declaration of +# a symbol. add_flow_target(LINK_TEST NAME flowlinktest SRCS LinkTest.cpp) target_link_libraries(flowlinktest PRIVATE flow stacktrace) set(IS_ARM_MAC NO) if(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") - set(IS_ARM_MAC YES) + set(IS_ARM_MAC YES) endif() foreach(ft flow flow_sampling flowlinktest) - target_include_directories( - ${ft} - PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}/include" - "${CMAKE_SOURCE_DIR}/contrib/libb64/include" - ) - - if (FLOW_USE_ZSTD) - target_include_directories(${ft} PRIVATE SYSTEM ${ZSTD_LIB_INCLUDE_DIR}) - endif() - if (USE_JEMALLOC) - target_include_directories(${ft} PRIVATE ${jemalloc_INCLUDE_DIRS}) - endif() - - target_link_libraries(${ft} PRIVATE stacktrace) - target_link_libraries(${ft} PUBLIC fmt::fmt SimpleOpt crc32 libb64) - if(UNIX AND NOT APPLE) - target_link_libraries(${ft} PRIVATE folly_memcpy) - target_compile_definitions(${ft} PRIVATE WITH_FOLLY_MEMCPY) - endif() - - if(NOT APPLE AND NOT WIN32) - set(FLOW_LIBS ${FLOW_LIBS} rt) - elseif(WIN32) - target_link_libraries(${ft} PUBLIC winmm.lib) - target_link_libraries(${ft} PUBLIC psapi.lib) - endif() - - if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") - set(FLOW_LIBS ${FLOW_LIBS} execinfo devstat) - find_library(EIO eio) - if(EIO) - target_link_libraries(${ft} PUBLIC ${EIO}) - endif() - endif() - target_link_libraries(${ft} PRIVATE ${FLOW_LIBS}) - - if(USE_VALGRIND) - target_link_libraries(${ft} PUBLIC Valgrind) - endif() - - target_link_libraries(${ft} PUBLIC OpenSSL::SSL) - target_link_libraries(${ft} PUBLIC Threads::Threads ${CMAKE_DL_LIBS}) - target_link_libraries(${ft} PUBLIC boost_target) - - if(APPLE) - find_library(IO_KIT IOKit) - find_library(CORE_FOUNDATION CoreFoundation) - target_link_libraries(${ft} PRIVATE ${IO_KIT} ${CORE_FOUNDATION}) + target_include_directories( + ${ft} + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}/include" + "${CMAKE_SOURCE_DIR}/contrib/libb64/include") + + if(FLOW_USE_ZSTD) + target_include_directories(${ft} PRIVATE SYSTEM ${ZSTD_LIB_INCLUDE_DIR}) + endif() + if(USE_JEMALLOC) + target_include_directories(${ft} PRIVATE ${jemalloc_INCLUDE_DIRS}) + endif() + + target_link_libraries(${ft} PRIVATE stacktrace) + target_link_libraries(${ft} PUBLIC fmt::fmt SimpleOpt crc32 libb64) + if(UNIX AND NOT APPLE) + target_link_libraries(${ft} PRIVATE folly_memcpy) + target_compile_definitions(${ft} PRIVATE WITH_FOLLY_MEMCPY) + endif() + + if(NOT APPLE AND NOT WIN32) + set(FLOW_LIBS ${FLOW_LIBS} rt) + elseif(WIN32) + target_link_libraries(${ft} PUBLIC winmm.lib) + target_link_libraries(${ft} PUBLIC psapi.lib) + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(FLOW_LIBS ${FLOW_LIBS} execinfo devstat) + find_library(EIO eio) + if(EIO) + target_link_libraries(${ft} PUBLIC ${EIO}) endif() - find_package(Coroutines COMPONENTS Experimental Final REQUIRED) - target_link_libraries(${ft} PUBLIC std::coroutines) + endif() + target_link_libraries(${ft} PRIVATE ${FLOW_LIBS}) + + if(USE_VALGRIND) + target_link_libraries(${ft} PUBLIC Valgrind) + endif() + + target_link_libraries(${ft} PUBLIC OpenSSL::SSL) + target_link_libraries(${ft} PUBLIC Threads::Threads ${CMAKE_DL_LIBS}) + target_link_libraries(${ft} PUBLIC boost_target) + + if(APPLE) + find_library(IO_KIT IOKit) + find_library(CORE_FOUNDATION CoreFoundation) + target_link_libraries(${ft} PRIVATE ${IO_KIT} ${CORE_FOUNDATION}) + endif() + find_package( + Coroutines + COMPONENTS Experimental Final + REQUIRED) + target_link_libraries(${ft} PUBLIC std::coroutines) endforeach() if(OPEN_FOR_IDE) - # AcAC requires actor transpiler - add_library(acac OBJECT acac.cpp) + # AcAC requires actor transpiler + add_library(acac OBJECT acac.cpp) else() - add_executable(acac acac.cpp) + add_executable(acac acac.cpp) endif() target_link_libraries(acac PUBLIC flow boost_target_program_options) target_compile_definitions(flow_sampling PRIVATE -DENABLE_SAMPLING) if(WIN32) - add_dependencies(flow_sampling_actors flow_actors) + add_dependencies(flow_sampling_actors flow_actors) endif() if(OPEN_FOR_IDE) - add_library(mkcert OBJECT MkCertCli.cpp) + add_library(mkcert OBJECT MkCertCli.cpp) else() - add_executable(mkcert MkCertCli.cpp) + add_executable(mkcert MkCertCli.cpp) endif() target_link_libraries(mkcert PUBLIC flow) diff --git a/flow/acac.cpp b/flow/acac.cpp index 8d0dca0b2e8..88e98d0de4e 100644 --- a/flow/acac.cpp +++ b/flow/acac.cpp @@ -1,19 +1,40 @@ +/* + * acac.cpp + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2024 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flow/ActorContext.h" + #ifdef WITH_ACAC #include #include #include #include +#include +#include #include #include -#include #include #include #include -#include "flow/ActorContext.h" - std::unordered_map loadUIDActorMapping(const std::string& build_directory_path) { std::unordered_map identifierToActor; @@ -51,13 +72,13 @@ void dumpActorContextTree(std::ostream& stream, const std::unordered_map& identifierToActor) { std::unordered_map> spawnInfo; - for (const auto& [actorID, _1, parentID] : decoded.context) { - spawnInfo[parentID].push_back(actorID); + for (const auto& item : decoded.context) { + spawnInfo[item.spawner].push_back(item.id); } std::unordered_map actorNames; - for (const auto& [actorID, actorIdentifier, _1] : decoded.context) { - actorNames[actorID] = identifierToActor.at(actorIdentifier); + for (const auto& item : decoded.context) { + actorNames[item.id] = identifierToActor.at(item.identifier); } // 2-space indentation @@ -86,13 +107,19 @@ void dumpActorContextTree(std::ostream& stream, void dumpActorContextStack(std::ostream& stream, const DecodedActorContext& decoded, const std::unordered_map& identifierToActor) { - for (const auto& [actorID, actorIdentifier, spawnerActorID] : decoded.context) { - const std::string& actorName = identifierToActor.at(actorIdentifier); - stream << std::setw(12) << actorID << " " << actorName - << (actorID == decoded.currentRunningActor ? " " : "") << std::endl; + for (const auto& item : decoded.context) { + const std::string& actorName = identifierToActor.at(item.identifier); + stream << std::setw(12) << item.id << " " << actorName + << (item.id == decoded.currentRunningActor ? " " : "") << std::endl; } } +auto decodeFromStream(std::istream& stream) { + using istream_iterator = std::istream_iterator; + std::string encoded = std::accumulate(istream_iterator(stream), istream_iterator(), std::string()); + return decodeActorContext(encoded); +} + void decodeClass(std::ostream& stream, const std::string& classIdentifier, const std::unordered_map& identifierToActor) { @@ -100,13 +127,27 @@ void decodeClass(std::ostream& stream, stream << classIdentifier << " -- " << identifierToActor.at(uid) << std::endl; } +class StuckActorDetector { + std::unordered_map identifierToActor; + std::unordered_map aliveActors; + +public: + StuckActorDetector(std::unordered_map identifierToActor_) + : identifierToActor(std::move(identifierToActor_)) {} +}; + +void detectStuckActor(const std::unordered_map& identifierToActor) { + StuckActorDetector sad(identifierToActor); +} + namespace bpo = boost::program_options; int main(int argc, char* argv[]) { bpo::options_description desc("Options"); desc.add_options()("help", "Print help message")("fdb-build-directory", bpo::value(), "Build directory")( - "decode-class", bpo::value(), "Decode a class key"); + "decode-class", bpo::value(), "Decode a class key")("stuck-actors", + "Try to identify the ACTOR that is stucked"); bpo::variables_map varMap; bpo::store(bpo::parse_command_line(argc, argv, desc), varMap); @@ -129,18 +170,8 @@ int main(int argc, char* argv[]) { return 0; } - std::string encodedContext; - while (std::cin) { - std::string line; - std::getline(std::cin, line); - boost::trim(line); - // libb64 will generate newline every 72 characters, which will be encoded as "\0xa" - // in the TraceEvent log file. - boost::replace_all(line, "\\x0a", "\n"); - encodedContext += line; - } + const auto decodedActorContext = decodeFromStream(std::cin); - const auto decodedActorContext = decodeActorContext(encodedContext); switch (decodedActorContext.dumpType) { case ActorContextDumpType::FULL_CONTEXT: dumpActorContextTree(std::cout, decodedActorContext, lib); diff --git a/flow/actorcompiler/ActorCompiler.cs b/flow/actorcompiler/ActorCompiler.cs index b158280f246..9cd41f6c6d4 100644 --- a/flow/actorcompiler/ActorCompiler.cs +++ b/flow/actorcompiler/ActorCompiler.cs @@ -27,6 +27,25 @@ namespace actorcompiler { + class ActorInformation + { + public UInt64 guid1 { get; set; } + public UInt64 guid2 { get; set; } + public string fileName { get; set; } + public int lineNumber { get; set; } + public string type { get; set; } + public string name { get; set; } + + public static (UInt64, UInt64) GetGuidPair() + { + var guid = Guid.NewGuid(); + var bytes = guid.ToByteArray(); + var ulong1 = BitConverter.ToUInt64(bytes, 0); + var ulong2 = BitConverter.ToUInt64(bytes, 8); + return (ulong1, ulong2); + } + } + class TypeSwitch { object value; @@ -443,7 +462,7 @@ class ActorCompiler whenCount = 0; string This; bool generateProbes; - public Dictionary<(ulong, ulong), string> uidObjects { get; private set; } + public List uidObjects; public ActorCompiler( Actor actor, @@ -458,7 +477,7 @@ bool generateProbes this.isTopLevel = isTopLevel; this.LineNumbersEnabled = lineNumbersEnabled; this.generateProbes = generateProbes; - this.uidObjects = new Dictionary<(ulong, ulong), string>(); + this.uidObjects = new List(); FindState(); } @@ -543,23 +562,38 @@ private void WriteActorClass(TextWriter writer, string fullStateClassName, Funct writer.WriteLine("\tusing FastAllocated<{0}>::operator new;", fullClassName); writer.WriteLine("\tusing FastAllocated<{0}>::operator delete;", fullClassName); - var actorIdentifierKey = this.sourceFile + ":" + this.actor.name; - var actorIdentifier = GetUidFromString(actorIdentifierKey); - uidObjects.Add((actorIdentifier.Item1, actorIdentifier.Item2), actorIdentifierKey); - // NOTE UL is required as a u64 postfix for large integers, otherwise Clang would complain - writer.WriteLine( - "\tstatic constexpr ActorIdentifier __actorIdentifier = UID({0}UL, {1}UL);", - actorIdentifier.Item1, - actorIdentifier.Item2 - ); - writer.WriteLine("\tActiveActorHelper activeActorHelper;"); + { + var actorIdentifier = ActorInformation.GetGuidPair(); + this.uidObjects.Add( + new ActorInformation + { + guid1 = actorIdentifier.Item1, + guid2 = actorIdentifier.Item2, + fileName = this.sourceFile, + lineNumber = this.actor.SourceLine, + type = "ACTOR", + name = this.actor.name + } + ); + + writer.WriteLine("#if ACTOR_MONITORING != ACTOR_MONITORING_DISABLED"); + writer.WriteLine( + "\tstatic constexpr ActorMonitoring::ActorIdentifier __actorIdentifier = ActorMonitoring::ActorIdentifier({0}ULL, {1}ULL);", + actorIdentifier.Item1, + actorIdentifier.Item2 + ); + writer.WriteLine("\tActorMonitoring::ActiveActorHelper activeActorHelper;"); + writer.WriteLine("#endif // ACTOR_MONITORING"); + } writer.WriteLine("#pragma clang diagnostic push"); writer.WriteLine("#pragma clang diagnostic ignored \"-Wdelete-non-virtual-dtor\""); if (actor.returnType != null) writer.WriteLine( @" void destroy() override {{ +#if ACTOR_MONITORING != ACTOR_MONITORING_DISABLED activeActorHelper.~ActiveActorHelper(); +#endif // ACTOR_MONITORING static_cast*>(this)->~Actor(); operator delete(this); }}", @@ -568,7 +602,9 @@ private void WriteActorClass(TextWriter writer, string fullStateClassName, Funct else writer.WriteLine( @" void destroy() {{ +#if ACTOR_MONITORING != ACTOR_MONITORING_DISABLED activeActorHelper.~ActiveActorHelper(); +#endif // ACTOR_MONITORING static_cast*>(this)->~Actor(); operator delete(this); }}" @@ -722,26 +758,68 @@ void ProbeEnter(Function fun, string name, int index = -1) index ); } - var blockIdentifier = GetUidFromString(fun.name); - fun.WriteLine("#ifdef WITH_ACAC"); + } + + void ProbeExit(Function fun, string name, int index = -1) + { + if (generateProbes) + { + fun.WriteLine("fdb_probe_actor_exit(\"{0}\", {1}, {2});", name, thisAddress, index); + } + } + + void ActorMonitoringEnter(Function fun, int sourceLine, string type) + { + var blockIdentifier = ActorInformation.GetGuidPair(); + fun.WriteLineUnindented("#if ACTOR_MONITORING != ACTOR_MONITORING_DISABLED"); fun.WriteLine( - "static constexpr ActorBlockIdentifier __identifier = UID({0}UL, {1}UL);", + "static constexpr ActorMonitoring::ActorBlockIdentifier __identifier = ActorMonitoring::ActorBlockIdentifier({0}ULL, {1}ULL);", blockIdentifier.Item1, blockIdentifier.Item2 ); + this.uidObjects.Add( + new ActorInformation + { + guid1 = blockIdentifier.Item1, + guid2 = blockIdentifier.Item2, + fileName = sourceFile, + lineNumber = sourceLine, + type = type, + name = this.actor.name + } + ); fun.WriteLine( - "ActorExecutionContextHelper __helper(static_cast<{0}*>(this)->activeActorHelper.actorID, __identifier);", + "ActorMonitoring::ActorExecutionContextHelper __helper(static_cast<{0}*>(this)->activeActorHelper.actorID, __identifier);", className ); - fun.WriteLine("#endif // WITH_ACAC"); + fun.WriteLineUnindented("#endif // ACTOR_MONITORING"); } - void ProbeExit(Function fun, string name, int index = -1) + void ActorMonitoringYield(Function fun, int sourceLine) { - if (generateProbes) - { - fun.WriteLine("fdb_probe_actor_exit(\"{0}\", {1}, {2});", name, thisAddress, index); - } + var blockIdentifier = ActorInformation.GetGuidPair(); + fun.WriteLineUnindented("#if ACTOR_MONITORING != ACTOR_MONITORING_DISABLED"); + fun.WriteLine( + "static constexpr ActorMonitoring::ActorBlockIdentifier __identifier = ActorMonitoring::ActorBlockIdentifier({0}ULL, {1}ULL);", + blockIdentifier.Item1, + blockIdentifier.Item2 + ); + this.uidObjects.Add( + new ActorInformation + { + guid1 = blockIdentifier.Item1, + guid2 = blockIdentifier.Item2, + fileName = sourceFile, + lineNumber = sourceLine, + type = "YIELD", + name = this.actor.name + } + ); + fun.WriteLine( + "ActorMonitoring::ActorYieldHelper __helper(static_cast<{0}*>(this)->activeActorHelper.actorID, __identifier);", + className + ); + fun.WriteLineUnindented("#endif // ACTOR_MONITORING"); } void ProbeCreate(Function fun, string name) @@ -1358,6 +1436,7 @@ void CompileStatement(ChooseStatement stmt, Context cx) functions.Add(string.Format("{0}#{1}", cbFunc.name, ch.Index), cbFunc); cbFunc.Indent(codeIndent); ProbeEnter(cbFunc, actor.name, ch.Index); + ActorMonitoringEnter(cbFunc, ch.Stmt.wait.FirstSourceLine, "FIRE"); cbFunc.WriteLine("{0};", exitFunc.call()); Function _overload = cbFunc.popOverload(); @@ -1401,6 +1480,7 @@ void CompileStatement(ChooseStatement stmt, Context cx) functions.Add(string.Format("{0}#{1}", errFunc.name, ch.Index), errFunc); errFunc.Indent(codeIndent); ProbeEnter(errFunc, actor.name, ch.Index); + ActorMonitoringEnter(errFunc, ch.Stmt.wait.FirstSourceLine, "ERROR"); errFunc.WriteLine("{0};", exitFunc.call()); TryCatch( cx.WithTarget(errFunc), @@ -1466,6 +1546,8 @@ void CompileStatement(ChooseStatement stmt, Context cx) } cx.target.WriteLine("loopDepth = 0;"); //cx.target.WriteLine("return 0;"); + ActorMonitoringYield(cx.target, stmt.FirstSourceLine); + if (!reachable) cx.unreachable(); } @@ -1940,23 +2022,26 @@ void WriteConstructor(Function body, TextWriter writer, string fullStateClassNam " : Actor<" + (actor.returnType == null ? "void" : actor.returnType) + ">()," ); constructor.WriteLine( - " {0}({1}),", + " {0}({1})", fullStateClassName, string.Join(", ", actor.parameters.Select(p => p.name)) ); - constructor.WriteLine(" activeActorHelper(__actorIdentifier)"); + constructor.WriteLineUnindented("#if ACTOR_MONITORING != ACTOR_MONITORING_DISABLED"); + constructor.WriteLine(" ,activeActorHelper(__actorIdentifier)"); + constructor.WriteLineUnindented("#endif // ACTOR_MONITORING"); constructor.Indent(-1); constructor.WriteLine("{"); constructor.Indent(+1); ProbeEnter(constructor, actor.name); + ActorMonitoringEnter(constructor, actor.SourceLine, "SPAWN"); - constructor.WriteLine("#ifdef ENABLE_SAMPLING"); + constructor.WriteLineUnindented("#ifdef ENABLE_SAMPLING"); constructor.WriteLine("this->lineage.setActorName(\"{0}\");", actor.name); constructor.WriteLine("LineageScope _(&this->lineage);"); // constructor.WriteLine("getCurrentLineage()->modify(&StackLineage::actorName) = \"{0}\"_sr;", actor.name); - constructor.WriteLine("#endif"); + constructor.WriteLineUnindented("#endif"); constructor.WriteLine("this->{0};", body.call()); diff --git a/flow/actorcompiler/ActorParser.cs b/flow/actorcompiler/ActorParser.cs index 64af3e4f40b..babff630acb 100644 --- a/flow/actorcompiler/ActorParser.cs +++ b/flow/actorcompiler/ActorParser.cs @@ -328,7 +328,7 @@ class ActorParser string sourceFile; ErrorMessagePolicy errorMessagePolicy; public bool generateProbes; - public Dictionary<(ulong, ulong), string> uidObjects { get; private set; } + public List uidObjects; public ActorParser( string text, @@ -340,7 +340,7 @@ bool generateProbes this.sourceFile = sourceFile; this.errorMessagePolicy = errorMessagePolicy; this.generateProbes = generateProbes; - this.uidObjects = new Dictionary<(ulong, ulong), string>(); + this.uidObjects = new List(); tokens = Tokenize(text).Select(t => new Token { Value = t }).ToArray(); CountParens(); //if (sourceFile.EndsWith(".h")) LineNumbersEnabled = false; @@ -460,9 +460,7 @@ public void Write(System.IO.TextWriter writer, string destFileName) generateProbes ); actorCompiler.Write(actorWriter); - actorCompiler - .uidObjects.ToList() - .ForEach(x => this.uidObjects.TryAdd(x.Key, x.Value)); + this.uidObjects.AddRange(actorCompiler.uidObjects); string[] actorLines = actorWriter.ToString().Split('\n'); diff --git a/flow/actorcompiler/Program.cs b/flow/actorcompiler/Program.cs index 8072575fe3d..9dd05c8c624 100644 --- a/flow/actorcompiler/Program.cs +++ b/flow/actorcompiler/Program.cs @@ -85,13 +85,18 @@ public static int Main(string[] args) using (var outputStream = new StreamWriter(outputtmp)) { - foreach (var entry in parser.uidObjects) + // FIXME The only reason this ugly format is used is that System.Text.Json is not supported + // in the build environment of Mono/.NET framework. + foreach (var item in parser.uidObjects) { outputStream.WriteLine( - "{0}|{1}|{2}", - entry.Key.Item1, - entry.Key.Item2, - entry.Value + "{0}|{1}|{2}|{3}|{4}|{5}", + item.guid1, + item.guid2, + item.fileName, + item.lineNumber, + item.type, + item.name ); } } diff --git a/flow/include/flow/ActorContext.h b/flow/include/flow/ActorContext.h index deddae853d9..480cb4719e6 100644 --- a/flow/include/flow/ActorContext.h +++ b/flow/include/flow/ActorContext.h @@ -21,49 +21,98 @@ #ifndef FLOW_ACTOR_CONTEXT_H #define FLOW_ACTOR_CONTEXT_H -#ifdef WITH_ACAC +#define ACTOR_MONITORING_DISABLED 0 +#define ACTOR_MONITORING_MINIMAL 1 +#define ACTOR_MONITORING_FULL 2 + +#if ACTOR_MONITORING != ACTOR_MONITORING_DISABLED #include #include -#include +#include +#include #include -#include "flow/FastAlloc.h" -#include "flow/FastRef.h" -#include "flow/IRandom.h" +#include "flow/GUID.h" + +namespace ActorMonitoring { + +// The unique identifier for the given actor +// It should be a UID. Yet we use a string_view to hint. Generated by ActorCompiler.cs +using ActorIdentifier = GUID; + +// The unique identifier for each part of the ACTOR. Generated by ActorCompiler.cs +using ActorBlockIdentifier = GUID; -using ActorIdentifier = UID; +// The identifier for a running actor, analogus to PID/ThreadID. It is used in the runtime. using ActorID = uint64_t; static constexpr ActorID INVALID_ACTOR_ID = std::numeric_limits::max(); static constexpr ActorID INIT_ACTOR_ID = 0; -struct ActiveActor { - ActorIdentifier identifier = ActorIdentifier(); - ActorID id = ActorID(); - double spawnTime = double(); +namespace { + +struct ActorInfoMinimal { + // The identifier of the type of the ACTOR + ActorIdentifier identifier; + + // The identifier of the ACTOR + ActorID id; + + // The identifier of the ACTOR that spawns this ACTOR + ActorID spawner; + + ActorInfoMinimal(); + ActorInfoMinimal(const ActorIdentifier& identifier_, const ActorID id_, const ActorID spawner_); + + template + void serialize(Ar& ar) { + serializer(ar, identifier, id, spawner); + } +}; + +struct ActorInfoFull : ActorInfoMinimal { + // The time the actor spawns + double spawnTime; + + // The time the actor being resumed lastly + double lastResumeTime; + + // The place the actor yields + ActorBlockIdentifier yieldBlockID; + + // The time actor yielded lastly + double lastYieldTime; - ActorID spawner = INVALID_ACTOR_ID; + // Number of times the actor being resumed + uint64_t numResumes; - ActiveActor(); - explicit ActiveActor(const ActorIdentifier& identifier_, - const ActorID& id_, - const ActorID& spawnerID_ = INVALID_ACTOR_ID); + ActorInfoFull(); + ActorInfoFull(const ActorIdentifier& identifier_, const ActorID id_, const ActorID spawner_); template void serialize(Ar& ar) { - serializer(ar, identifier, id, spawnTime, spawner); + ActorInfoMinimal::serialize(ar); + serializer(ar, spawnTime, lastResumeTime, yieldBlockID, lastYieldTime, numResumes); } }; -using ActorBlockIdentifier = UID; +} // namespace + +#if ACTOR_MONITORING == ACTOR_MONITORING_MINIMAL +using ActorInfo = ActorInfoMinimal; +#elif ACTOR_MONITORING == ACTOR_MONITORING_FULL +using ActorInfo = ActorInfoFull; +#endif + +using ActiveActor = ActorInfo; struct ActorExecutionContext { ActorID actorID; ActorBlockIdentifier blockIdentifier; - explicit ActorExecutionContext(const ActorID actorID_, const ActorBlockIdentifier blockIdentifier_) + ActorExecutionContext(const ActorID actorID_, const ActorBlockIdentifier blockIdentifier_) : actorID(actorID_), blockIdentifier(blockIdentifier_) {} }; @@ -75,7 +124,7 @@ class ActiveActorHelper { public: ActorID actorID; - ActiveActorHelper(const ActorIdentifier& actorIdentifier); + explicit ActiveActorHelper(const ActorIdentifier& actorIdentifier); ~ActiveActorHelper(); }; @@ -85,54 +134,61 @@ class ActorExecutionContextHelper { ~ActorExecutionContextHelper(); }; +class ActorYieldHelper { +public: + ActorYieldHelper(const ActorID& actorID_, const ActorBlockIdentifier& blockIdentifier_); +}; + enum class ActorContextDumpType : uint8_t { FULL_CONTEXT, CURRENT_STACK, CURRENT_CALL_BACKTRACE, }; -// Encode the current actor context into a string +// Encode the current actor context into a base64 string extern std::string encodeActorContext(const ActorContextDumpType dumpType = ActorContextDumpType::FULL_CONTEXT); // Encode the current actor call backtrace extern void dumpActorCallBacktrace(); struct DecodedActorContext { - struct ActorInfo { - ActorID id; - ActorIdentifier identifier; - ActorID spawnerID; - - ActorInfo(const ActorID& _id, const ActorIdentifier& _identifier, const ActorID& _spawnerID) - : id(_id), identifier(_identifier), spawnerID(_spawnerID) {} - }; ActorID currentRunningActor; std::vector context; + uint64_t dumpFields; ActorContextDumpType dumpType; }; // Decode the serialized actor context to DecodedActorContext DecodedActorContext decodeActorContext(const std::string& caller); -#else // WITH_ACAC +} // namespace ActorMonitoring -#include +#else // ACTOR_MONITORING != ACTOR_MONITORING_DISABLED -#include "flow/IRandom.h" +#include "flow/GUID.h" +namespace ActorMonitoring { using ActorID = uint64_t; -using ActorIdentifier = UID; -using ActorBlockIdentifier = UID; +using ActorIdentifier = GUID; +using ActorBlockIdentifier = GUID; struct ActorExecutionContext {}; +struct ActorInfo {}; struct ActiveActor {}; struct ActiveActorHelper { ActiveActorHelper(const ActorIdentifier&) {} }; struct ActorExecutionContextHelper { - ActorExecutionContextHelper(const ActorID&, const ActorBlockIdentifier&); + ActorExecutionContextHelper(const ActorID&, const ActorBlockIdentifier&) {} }; +struct ActorYieldHelper { + ActorYieldHelper(const ActorID&, const ActorBlockIdentifier&) {} +}; + +inline void dumpActorCallBacktrace() {} + +} // namespace ActorMonitoring -#endif // WITH_ACAC +#endif // ACTOR_MONITORING != ACTOR_MONITORING_DISABLED #endif // FLOW_ACTOR_CONTEXT_H diff --git a/flow/include/flow/flow.h b/flow/include/flow/flow.h index 49d5505bd04..8cfc2990e0b 100644 --- a/flow/include/flow/flow.h +++ b/flow/include/flow/flow.h @@ -20,6 +20,7 @@ #ifndef FLOW_FLOW_H #define FLOW_FLOW_H + #include "flow/ActorContext.h" #include "flow/Arena.h" #include "flow/FastRef.h"