Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions cmake/CompileActorCompiler.cmake
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
find_package(Python3 REQUIRED COMPONENTS Interpreter)

find_program(MCS_EXECUTABLE mcs)
find_program(MONO_EXECUTABLE mono)

set(ACTORCOMPILER_PY_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler_py/__main__.py
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler_py/errors.py
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler_py/actor_parser.py
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler_py/actor_compiler.py)

set(ACTORCOMPILER_CSPROJ
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/actorcompiler.csproj)

set(ACTORCOMPILER_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/ActorCompiler.cs
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/ActorParser.cs
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/ParseTree.cs
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/Program.cs
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/Properties/AssemblyInfo.cs)

set(ACTOR_COMPILER_REFERENCES
"-r:System,System.Core,System.Xml.Linq,System.Data.DataSetExtensions,Microsoft.CSharp,System.Data,System.Xml"
)

add_custom_target(actorcompiler_py DEPENDS ${ACTORCOMPILER_PY_SRCS})

if(WIN32)
add_executable(actorcompiler ${ACTORCOMPILER_SRCS})
target_compile_options(actorcompiler PRIVATE "/langversion:6")
add_executable(actorcompiler_csharp ${ACTORCOMPILER_SRCS})
target_compile_options(actorcompiler_csharp PRIVATE "/langversion:6")
set_property(
TARGET actorcompiler
TARGET actorcompiler_csharp
PROPERTY VS_DOTNET_REFERENCES
"System"
"System.Core"
Expand All @@ -23,6 +38,10 @@ if(WIN32)
"Microsoft.CSharp"
"System.Data"
"System.Xml")
set(ACTORCOMPILER_CSHARP_COMMAND $<TARGET_FILE:actorcompiler_csharp>
CACHE INTERNAL "Command to run the C# actor compiler")
add_custom_target(actorcompiler)
add_dependencies(actorcompiler actorcompiler_csharp actorcompiler_py)
elseif(CSHARP_USE_MONO)
add_custom_command(
OUTPUT actorcompiler.exe
Expand All @@ -31,11 +50,23 @@ elseif(CSHARP_USE_MONO)
DEPENDS ${ACTORCOMPILER_SRCS}
COMMENT "Compile actor compiler"
VERBATIM)
add_custom_target(actorcompiler
add_custom_target(actorcompiler_csharp
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/actorcompiler.exe)
set(actor_exe "${CMAKE_CURRENT_BINARY_DIR}/actorcompiler.exe")
set(ACTORCOMPILER_CSHARP_COMMAND ${MONO_EXECUTABLE} ${actor_exe}
CACHE INTERNAL "Command to run the C# actor compiler")
add_custom_target(actorcompiler)
add_dependencies(actorcompiler actorcompiler_csharp actorcompiler_py)
else()
dotnet_build(${ACTORCOMPILER_CSPROJ} SOURCE ${ACTORCOMPILER_SRCS})
set(actor_exe "${actorcompiler_EXECUTABLE_PATH}")
message(STATUS "Actor compiler path: ${actor_exe}")
# dotnet_build already creates a target named 'actorcompiler', so we just add Python dependency
add_dependencies(actorcompiler actorcompiler_py)
set(ACTORCOMPILER_CSHARP_COMMAND ${actor_exe}
CACHE INTERNAL "Command to run the C# actor compiler")
endif()

set(ACTORCOMPILER_COMMAND
${Python3_EXECUTABLE} -m flow.actorcompiler_py
CACHE INTERNAL "Command to run the actor compiler")
30 changes: 13 additions & 17 deletions cmake/FlowCommands.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -263,25 +263,21 @@ function(add_flow_target)
endforeach()

list(APPEND generated_files ${out_file})
if(WIN32)
add_custom_command(
OUTPUT "${out_file}"
COMMAND $<TARGET_FILE:actorcompiler> "${in_file}" "${out_file}"
${actor_compiler_flags}
if(ACTORCOMPILER_CSHARP_COMMAND)
set(py_out_file "${out_file}.py_gen")
set(cs_out_file "${out_file}.cs_gen")
add_custom_command(OUTPUT "${out_file}"
COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_SOURCE_DIR}"
${ACTORCOMPILER_COMMAND} "${in_file}" "${py_out_file}" ${actor_compiler_flags}
COMMAND ${ACTORCOMPILER_CSHARP_COMMAND} "${in_file}" "${cs_out_file}" ${actor_compiler_flags}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/flow/actorcompiler_py/compare_actor_output.py "${cs_out_file}" "${py_out_file}"
COMMAND ${CMAKE_COMMAND} -E copy "${py_out_file}" "${out_file}"
DEPENDS "${in_file}" actorcompiler
COMMENT "Compile actor: ${src}")
elseif(CSHARP_USE_MONO)
add_custom_command(
OUTPUT "${out_file}"
COMMAND ${MONO_EXECUTABLE} ${actor_exe} "${in_file}" "${out_file}"
${actor_compiler_flags} > /dev/null
DEPENDS "${in_file}" actorcompiler
COMMENT "Compile actor: ${src}")
COMMENT "Compile and compare actor: ${src}")
else()
add_custom_command(
OUTPUT "${out_file}"
COMMAND ${actor_exe} "${in_file}" "${out_file}"
${actor_compiler_flags} > /dev/null
add_custom_command(OUTPUT "${out_file}"
COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_SOURCE_DIR}"
${ACTORCOMPILER_COMMAND} "${in_file}" "${out_file}" ${actor_compiler_flags}
DEPENDS "${in_file}" actorcompiler
COMMENT "Compile actor: ${src}")
endif()
Expand Down
84 changes: 84 additions & 0 deletions flow/actorcompiler_py/Actor checklist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
Compile issues:

- wait() must always assign the resulting value to a newly declared variable.

- Variables used across a "wait() boundary" must be declared state


Remember to:

- Add useful ASSERT()s

- Add BUGGIFY() statements to expose rare cases to simulation

- Add TEST() statements to any conditional/rare cases

- Comment invariants, strategy, preconditions, tricky stuff when you
figure it out, even if it's not "your" code.

- Factor common asynchronous control flows to use composition
of generic actors such as:
waitForAll
timeout
splitFuture
recurring
smartQuorum
AsyncMap
broadcast
&&, ||
etc...

- Declare classes NonCopyable unless they are, and you know what
that means


Run time issues:

- Is the actor return type "void"? Make sure that some exception or timeout
will trigger eventually to clean up the actor.

- If you send a future to another future, a long-lived forwardPromise
actor is created--make sure that the event happens eventually to free
this actor.

- If you return a Future<T> instead of a T from an actor, a forwardPromise
actor is created with the same lifetime issues as above.

- Remember that parameters are internally passed to the actor as const &
and then copied into actor state variables

- When you use *GetReply() or LoadBalance(), the "server" responding to
your request might get your request multiple times.

- When you use getReply() instead of tryGetReply() you must ensure that the
actor will be cancelled if the service you are trying to connect to is
no longer available. (Otherwise, an infinite waiting loop)

- For each wait:

- An actor_cancelled exception can be thrown if the actor's return
value future is dropped.

- An exception can arrive instead of a value

- What happens if it never returns?

- If the client fulfilling the wait is coming over the network, you
might get the same request multiple times


Performance issues:

- Wait a little extra time before doing something time-consuming or
irreversible to see if it is still necessary.

- When waiting for a number of things, wait a little extra time to get
the stragglers. (See the SmartQuorum() generic actor)

- If asking another asynchronous server to do units of work, don't queue up more
work than is necessary to keep the server busy. Likewise, if you are
busy, let your own work queue fill up to signal your requester
that you are blocked. Also do this personally with managers assigning
you stuff.

- Pass all variables as "const &" if their size is greater than 8 bytes.
101 changes: 101 additions & 0 deletions flow/actorcompiler_py/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Allow running the actorcompiler as a module:
python3 -m flow.actorcompiler input.actor.cpp output.g.cpp
"""
from __future__ import annotations

import argparse
import os
import stat
import sys
from pathlib import Path

from .actor_parser import ActorParser, ErrorMessagePolicy
from .errors import ActorCompilerError


def overwrite_by_move(target: Path, temporary: Path) -> None:
if target.exists():
target.chmod(stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
target.unlink()
os.replace(temporary, target)
target.chmod(stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)


def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="actorcompiler",
description="Python port of the Flow actor compiler",
add_help=False,
usage="actorcompiler <input> <output> [--disable-diagnostics] [--generate-probes]",
)
parser.add_argument("input", nargs="?")
parser.add_argument("output", nargs="?")
parser.add_argument("--disable-diagnostics", action="store_true")
parser.add_argument("--generate-probes", action="store_true")
parser.add_argument("--help", action="help", help=argparse.SUPPRESS)
args = parser.parse_args()
if not args.input or not args.output:
parser.print_usage(sys.stderr)
sys.exit(100)
return args


def main() -> int:
args = parse_arguments()
input_path = Path(args.input)
output_path = Path(args.output)
output_tmp = output_path.with_suffix(output_path.suffix + ".tmp")
output_uid = output_path.with_suffix(output_path.suffix + ".uid")

policy = ErrorMessagePolicy()
policy.disable_diagnostics = args.disable_diagnostics

try:
print("actorcompiler", " ".join(sys.argv[1:]))
text = input_path.read_text()
parser = ActorParser(
text,
str(input_path).replace("\\", "/"),
policy,
args.generate_probes,
)

with output_tmp.open("w", newline="\n") as out_file:
parser.write(out_file, str(output_path).replace("\\", "/"))
overwrite_by_move(output_path, output_tmp)

with output_tmp.open("w", newline="\n") as uid_file:
for (hi, lo), value in parser.uid_objects.items():
uid_file.write(f"{hi}|{lo}|{value}\n")
overwrite_by_move(output_uid, output_tmp)

return 0
except ActorCompilerError as exc:
print(
f"{input_path}({exc.source_line}): error FAC1000: {exc}",
file=sys.stderr,
)
if output_tmp.exists():
output_tmp.unlink()
if output_path.exists():
output_path.chmod(stat.S_IWUSR | stat.S_IRUSR)
output_path.unlink()
return 1
except Exception as exc: # pylint: disable=broad-except
import traceback
traceback.print_exc()
print(
f"{input_path}(1): error FAC2000: Internal {exc}",
file=sys.stderr,
)
if output_tmp.exists():
output_tmp.unlink()
if output_path.exists():
output_path.chmod(stat.S_IWUSR | stat.S_IRUSR)
output_path.unlink()
return 3


if __name__ == "__main__":
sys.exit(main())
Loading