diff --git a/.typos.toml b/.typos.toml index 99a474f60..0fe998302 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,3 +1,4 @@ [default.extend-words] inout = "inout" ocur = "ocur" +ba = "ba" diff --git a/doc/autogen/reserved-keywords.txt b/doc/autogen/reserved-keywords.txt index 879d7964f..a0f0b976d 100644 --- a/doc/autogen/reserved-keywords.txt +++ b/doc/autogen/reserved-keywords.txt @@ -9,6 +9,7 @@ any assert assert-exception attribute +barrier begin bitfield bool diff --git a/doc/autogen/types/barrier.rst b/doc/autogen/types/barrier.rst new file mode 100644 index 000000000..f6cd4ec4a --- /dev/null +++ b/doc/autogen/types/barrier.rst @@ -0,0 +1,32 @@ +.. rubric:: Methods + +.. spicy:method:: barrier::abort barrier() abort False void () + + Aborts the barrier, causing any waiting parties to throw a + ``BarrierAborted`` exception. This has no effect if the barrier is + already released or aborted. + +.. spicy:method:: barrier::arrive barrier() arrive False void () + + Signals a party's arrival at the barrier, potentially releasing it if + the expected number of parties have been seen now. This has no effect + if the barrier is already released or aborted. + +.. spicy:method:: barrier::arrive_and_wait barrier() arrive_and_wait False void () + + Convenience method combining a `arrive()` with an immediately + following `wait()`. + +.. spicy:method:: barrier::wait barrier() wait False void () + + Blocks the caller until the barrier is released by the expected number + of parties arriving. If the barrier is already released, it will + return immediately. If the barrier gets aborted before or during the + wait, the method will throw a ``BarrierAborted`` exception. + +.. rubric:: Operators + +.. spicy:operator:: barrier::Call barrier() barrier(uint) + + Creates a barrier that will wait for the given number of parties. + diff --git a/doc/programming/language/types.rst b/doc/programming/language/types.rst index d3d7ee74e..ad4a8f12d 100644 --- a/doc/programming/language/types.rst +++ b/doc/programming/language/types.rst @@ -25,6 +25,36 @@ This type supports the :ref:`pack/unpack operators `. .. include:: /autogen/types/address.rst +.. _type_barrier: + +Barrier +------- + +A ``barrier`` provides a synchronization point for multiple parsing +stream, such as the two sides of a connection. It comes with an +expected number ``N`` of parties to reach the synchronization point. +Each party must call ``arrive()` to signal it has done so. Each party +may call ``wait()`` to block until all parties have arrived. + +.. note:: + + It's possible to create dead-lock situations with barriers if you + aren't careful. The easiest way to do so is waiting for a barrier + on which nobody ever calls ``arrive()``. If a deadlock occurs, + it's up to the host application to eventually resolve the + situation. Zeek, for example, will eventually time out the + connection and clean up the parsing state. + +.. rubric:: Type + +- ``barrier(N)`` + +.. rubric:: Constants + +- ``barrier(N)``, ``barrier()`` (untyped) + +.. include:: /autogen/types/barrier.rst + .. _type_bitfield: Bitfield diff --git a/hilti/runtime/CMakeLists.txt b/hilti/runtime/CMakeLists.txt index acdbd3c65..bdc41fe50 100644 --- a/hilti/runtime/CMakeLists.txt +++ b/hilti/runtime/CMakeLists.txt @@ -35,6 +35,7 @@ set(SOURCES src/safe-math.cc src/type-info.cc src/types/address.cc + src/types/barrier.cc src/types/bytes.cc src/types/integer.cc src/types/port.cc @@ -133,6 +134,7 @@ add_executable( src/tests/main.cc src/tests/address.cc src/tests/backtrace.cc + src/tests/barrier.cc src/tests/bytes.cc src/tests/context.cc src/tests/debug-logger.cc diff --git a/hilti/runtime/include/exception.h b/hilti/runtime/include/exception.h index d555bb45f..c672c10f1 100644 --- a/hilti/runtime/include/exception.h +++ b/hilti/runtime/include/exception.h @@ -99,6 +99,12 @@ HILTI_EXCEPTION(AssertionFailure, RuntimeError) */ HILTI_EXCEPTION(AttributeNotSet, RuntimeError) +/** + * Exception triggered when a barrier synchronization is either explicitly or + * implicitly aborted. + */ +HILTI_EXCEPTION(BarrierAborted, RuntimeError) + /** * Exception triggered when a division by zero is attempted. */ diff --git a/hilti/runtime/include/fiber.h b/hilti/runtime/include/fiber.h index 619502e2d..310dc8b95 100644 --- a/hilti/runtime/include/fiber.h +++ b/hilti/runtime/include/fiber.h @@ -176,11 +176,17 @@ class Fiber { /** Returns the fiber's type. */ auto type() { return _type; } + /** + * Returns true if the fiber is currently suspended due to waiting for a + * barrier. + */ + auto atBarrier() const { return _state == State::YieldedAtBarrier; } + /** Returns the fiber's stack buffer. */ const auto& stackBuffer() const { return _stack_buffer; } void run(); - void yield(); + void yield(bool at_barrier = false); void resume(); void abort(); @@ -189,7 +195,8 @@ class Fiber { bool isDone() { switch ( _state ) { case State::Running: - case State::Yielded: return false; + case State::Yielded: + case State::YieldedAtBarrier: return false; case State::Aborting: case State::Finished: @@ -226,7 +233,7 @@ class Fiber { friend void ::__fiber_run_trampoline(void* argsp); friend void ::__fiber_switch_trampoline(void* argsp); - enum class State { Init, Running, Aborting, Yielded, Idle, Finished }; + enum class State { Init, Running, Aborting, Yielded, YieldedAtBarrier, Idle, Finished }; void _yield(const char* tag); void _activate(const char* tag); @@ -275,7 +282,7 @@ class Fiber { std::ostream& operator<<(std::ostream& out, const Fiber& fiber); -extern void yield(); +extern void yield(bool at_barrier = false); /** * Checks that the current fiber has sufficient stack space left for @@ -328,6 +335,12 @@ class Resumable { /** Returns a handle to the currently running function. */ resumable::Handle* handle() { return _fiber.get(); } + /** + * Returns true if the function is currently suspended due to waiting for a + * barrier. + */ + auto atBarrier() const { return (! _done) && _fiber->atBarrier(); } + /** * Returns true if the function has completed orderly and provided a result. * If so, `get()` can be used to retrieve the result. diff --git a/hilti/runtime/include/type-info.h b/hilti/runtime/include/type-info.h index 059e5e38a..925078c01 100644 --- a/hilti/runtime/include/type-info.h +++ b/hilti/runtime/include/type-info.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -378,6 +379,9 @@ class Address : public detail::AtomicType {}; /** Type information for type ``any`. */ class Any : public detail::ValueLessType {}; +/** Type information for type ``barrier`. */ +class Barrier : public detail::AtomicType {}; + /** Type information for type ``bool`. */ class Bool : public detail::AtomicType {}; @@ -1148,6 +1152,7 @@ struct TypeInfo { Undefined, Address, Any, + Barrier, Bool, Bytes, BytesIterator, @@ -1198,6 +1203,7 @@ struct TypeInfo { union { type_info::Address* address; type_info::Any* any; + type_info::Barrier* barrier; type_info::Bool* bool_; type_info::Bytes* bytes; type_info::BytesIterator* bytes_iterator; @@ -1256,6 +1262,10 @@ struct TypeInfo { tag = Any; any = value; } + else if constexpr ( std::is_same_v ) { + tag = Barrier; + barrier = value; + } else if constexpr ( std::is_same_v ) { tag = Bool; bool_ = value; @@ -1450,6 +1460,10 @@ const Type* auxType(const type_info::Value& v) { assert(type.tag == TypeInfo::Any); return type.any; } + else if constexpr ( std::is_same_v ) { + assert(type.tag == TypeInfo::Barrier); + return type.barrier; + } else if constexpr ( std::is_same_v ) { assert(type.tag == TypeInfo::Bool); return type.bool_; @@ -1624,6 +1638,7 @@ const Type* auxType(const type_info::Value& v) { // Forward declare static built-in type information objects. extern TypeInfo address; extern TypeInfo any; +extern TypeInfo barrier; extern TypeInfo bool_; extern TypeInfo bytes_iterator; extern TypeInfo bytes; diff --git a/hilti/runtime/include/types/all.h b/hilti/runtime/include/types/all.h index ceae2e147..c504d680b 100644 --- a/hilti/runtime/include/types/all.h +++ b/hilti/runtime/include/types/all.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include diff --git a/hilti/runtime/include/types/barrier.h b/hilti/runtime/include/types/barrier.h new file mode 100644 index 000000000..1554803fd --- /dev/null +++ b/hilti/runtime/include/types/barrier.h @@ -0,0 +1,105 @@ +// Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details. + +#pragma once + +#include + +#include +#include + +#include +#include +#include +#include + +namespace hilti::rt { + +/** Represents HILTI's `barrier` type. */ +class Barrier { +public: + /** + * Constructs a barrier. + * + * @param parties number of parties that must arrive at the barrier before it is released + */ + explicit Barrier(const hilti::rt::integer::safe& expected_parties) : _expected(expected_parties) {} + + /** + * Default constructor creating a barrier expecting no parties, meaning + * that it will start out as released already. + */ + Barrier() = default; + + Barrier(const Barrier&) = default; + Barrier(Barrier&&) noexcept = default; + ~Barrier() = default; + + /** + * Blocks the caller until the barrier is released. If the barrier is + * already released, it will return immediately. If not, it will yield back + * to the runtime system, and re-check the barrier state when resumed. + * + * @throws BarrierAborted if the barrier is aborted either immediately at + * initial call time or at a later resume + */ + void wait(); + + /** + * Signals a party's arrival at the barrier, potentially releasing it if + * the expected number of parties have been seen now. This has no effect if + * the barrier is already released or aborted. + */ + void arrive(); + + /** + * Convenience method combining a `arrive()` with an immediately following + * `wait()`. + */ + void arrive_and_wait() { + arrive(); + wait(); + } + + /** + * Aborts operation of the barrier. That means that all parties waiting for + * it now or later, will receive a `BarrierAborted` exception. This method + * has no effect if the barrier has already been released. + */ + void abort(); + + /** + * Returns true if the expected number of parties has arrived at the + * barrier. + */ + bool isReleased() const { return _expected >= 0 && _expected == _arrived; } + + /** + * Returns true if the barrier received an abort() before it could get + * released. + */ + bool isAborted() const { return _expected < 0; } + + /** Returns true if the barrier has been released. */ + explicit operator bool() const { return isReleased(); } + + /** Returns a printable representation of the barrier's current state. */ + operator std::string() const; + + Barrier& operator=(const Barrier&) = default; + Barrier& operator=(Barrier&&) noexcept = default; + +private: + integer::safe _expected = 0; + integer::safe _arrived = 0; +}; + +namespace detail::adl { +inline std::string to_string(const hilti::rt::Barrier& x, adl::tag /*unused*/) { return std::string(x); } +} // namespace detail::adl + +inline std::ostream& operator<<(std::ostream& out, const Barrier& x) { + out << to_string(x); + return out; +} + +} // namespace hilti::rt diff --git a/hilti/runtime/src/exception.cc b/hilti/runtime/src/exception.cc index e81b2437c..91807eea3 100644 --- a/hilti/runtime/src/exception.cc +++ b/hilti/runtime/src/exception.cc @@ -15,6 +15,7 @@ HILTI_EXCEPTION_IMPL(RecoverableFailure) HILTI_EXCEPTION_IMPL(AssertionFailure) HILTI_EXCEPTION_IMPL(AttributeNotSet) +HILTI_EXCEPTION_IMPL(BarrierAborted) HILTI_EXCEPTION_IMPL(DivisionByZero) HILTI_EXCEPTION_IMPL(EnvironmentError) HILTI_EXCEPTION_IMPL(ExpiredReference) diff --git a/hilti/runtime/src/fiber.cc b/hilti/runtime/src/fiber.cc index 29e925166..eda519d27 100644 --- a/hilti/runtime/src/fiber.cc +++ b/hilti/runtime/src/fiber.cc @@ -455,15 +455,16 @@ void detail::Fiber::run() { switch ( _state ) { case State::Yielded: + case State::YieldedAtBarrier: case State::Idle: return; default: internalError(fmt("fiber: unexpected state (%d)", static_cast(_state))); } } -void detail::Fiber::yield() { +void detail::Fiber::yield(bool at_barrier) { assert(_state == State::Running); - _state = State::Yielded; + _state = (at_barrier ? State::YieldedAtBarrier : State::Yielded); _yield("yield"); if ( _state == State::Aborting ) @@ -471,12 +472,12 @@ void detail::Fiber::yield() { } void detail::Fiber::resume() { - assert(_state == State::Yielded); + assert(_state == State::Yielded || _state == State::YieldedAtBarrier); return run(); } void detail::Fiber::abort() { - assert(_state == State::Yielded); + assert(_state == State::Yielded || _state == State::YieldedAtBarrier); _state = State::Aborting; if ( ! context::detail::get(true) ) @@ -503,7 +504,7 @@ void detail::Fiber::destroy(std::unique_ptr f) { if ( f->isMain() ) return; - if ( f->_state == State::Yielded ) + if ( f->_state == State::Yielded || f->_state == State::YieldedAtBarrier ) f->abort(); auto* context = context::detail::get(true); @@ -600,13 +601,13 @@ void Resumable::yielded() { } } -void detail::yield() { +void detail::yield(bool at_barrier) { auto r = context::detail::get()->resumable; if ( ! r ) throw RuntimeError("'yield' in non-suspendable context"); - r->yield(); + r->yield(at_barrier); context::detail::get()->resumable = r; } diff --git a/hilti/runtime/src/tests/barrier.cc b/hilti/runtime/src/tests/barrier.cc new file mode 100644 index 000000000..54608a53d --- /dev/null +++ b/hilti/runtime/src/tests/barrier.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details. + +#include +#include +#include +#include "exception.h" + +using namespace hilti::rt; + +TEST_SUITE_BEGIN("Barrier"); + +TEST_CASE("construct") { + auto b1 = Barrier(0); + CHECK(b1.isReleased()); + CHECK(b1); + CHECK(! b1.isAborted()); + CHECK(b1.isReleased()); + + auto b2 = Barrier(3); + CHECK(! b2.isReleased()); + CHECK(! b2); + CHECK(! b2.isAborted()); + CHECK(! b2.isReleased()); +} + +TEST_CASE("three-parties") { + hilti::rt::init(); + + std::string x; + hilti::rt::Barrier b(3); + + auto p1 = [&](hilti::rt::resumable::Handle* r) { + x += "a"; + b.arrive_and_wait(); + CHECK(b.isReleased()); + x += "b"; + return hilti::rt::Nothing(); + }; + + auto p2 = [&](hilti::rt::resumable::Handle* r) { + x += "c"; + b.arrive_and_wait(); + CHECK(b.isReleased()); + x += "d"; + return hilti::rt::Nothing(); + }; + + auto p3 = [&](hilti::rt::resumable::Handle* r) { + x += "e"; + b.arrive_and_wait(); + CHECK(b.isReleased()); + x += "f"; + return hilti::rt::Nothing(); + }; + + auto r1 = hilti::rt::fiber::execute(p1); + REQUIRE(! r1); + CHECK(r1.atBarrier()); + CHECK(! b.isReleased()); + + auto r2 = hilti::rt::fiber::execute(p2); + REQUIRE(! r2); + CHECK(r2.atBarrier()); + CHECK(! b.isReleased()); + + auto r3 = hilti::rt::fiber::execute(p3); + REQUIRE(r3); + CHECK(! r3.atBarrier()); + CHECK(b.isReleased()); + + r1.resume(); + REQUIRE(r1); + CHECK(! r1.atBarrier()); + CHECK(b.isReleased()); + + r2.resume(); + REQUIRE(r2); + CHECK(! r2.atBarrier()); + CHECK(b.isReleased()); + + CHECK_NOTHROW(b.wait()); + + CHECK_EQ(x, "acefbd"); +} + +TEST_CASE("abort") { + hilti::rt::init(); + + SUBCASE("abort during wait") { + hilti::rt::Barrier b(3); + auto p = [&](hilti::rt::resumable::Handle* r) { + b.arrive_and_wait(); + b.arrive_and_wait(); + return hilti::rt::Nothing(); + }; + + auto r = hilti::rt::fiber::execute(p); + REQUIRE(! r); + CHECK(r.atBarrier()); + CHECK(! b.isReleased()); + CHECK(! b.isAborted()); + + r.resume(); + REQUIRE(! r); + CHECK(r.atBarrier()); + CHECK(! b.isReleased()); + CHECK(! b.isAborted()); + + b.abort(); + CHECK_THROWS_AS(b.wait(), BarrierAborted); + + CHECK_THROWS_AS(r.resume(), BarrierAborted); + REQUIRE(r); + CHECK(! r.atBarrier()); + CHECK(! b.isReleased()); + CHECK(b.isAborted()); + + CHECK_THROWS_AS(b.wait(), BarrierAborted); + } + + SUBCASE("abort after release") { + hilti::rt::Barrier b(1); + b.arrive(); + CHECK(b.isReleased()); + + CHECK_NOTHROW(b.wait()); + } +} + +TEST_SUITE_END(); diff --git a/hilti/runtime/src/tests/fiber.cc b/hilti/runtime/src/tests/fiber.cc index 6f06d7ebd..913315087 100644 --- a/hilti/runtime/src/tests/fiber.cc +++ b/hilti/runtime/src/tests/fiber.cc @@ -44,6 +44,7 @@ TEST_CASE("execute-void") { TEST_CASE("reuse-from-cache") { hilti::rt::init(); + hilti::rt::detail::Fiber::reset(); // reset cache and counters int x = 0; diff --git a/hilti/runtime/src/type-info.cc b/hilti/runtime/src/type-info.cc index 3bc06733a..6dd00ef2e 100644 --- a/hilti/runtime/src/type-info.cc +++ b/hilti/runtime/src/type-info.cc @@ -8,6 +8,7 @@ using namespace hilti::rt; TypeInfo type_info::address{std::nullopt, "address", new type_info::Address()}; TypeInfo type_info::any{std::nullopt, "any", new type_info::Any()}; +TypeInfo type_info::barrier{std::nullopt, "barrier", new type_info::Barrier()}; TypeInfo type_info::bool_{std::nullopt, "bool", new type_info::Bool()}; TypeInfo type_info::bytes{std::nullopt, "bytes", new type_info::Bytes()}; TypeInfo type_info::bytes_iterator{std::nullopt, "iterator", new type_info::BytesIterator()}; diff --git a/hilti/runtime/src/types/barrier.cc b/hilti/runtime/src/types/barrier.cc new file mode 100644 index 000000000..389ed910e --- /dev/null +++ b/hilti/runtime/src/types/barrier.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details. + +#include + +using namespace hilti::rt; + +void Barrier::wait() { + while ( true ) { + if ( isReleased() ) + return; + + if ( isAborted() ) + throw BarrierAborted("broken barrier"); + + detail::yield(true); + } +} + +void Barrier::arrive() { + if ( isReleased() || isAborted() ) + return; + + ++_arrived; +} + +void Barrier::abort() { + if ( ! isReleased() ) + _expected = -1; +} + +Barrier::operator std::string() const { + if ( isAborted() ) + return ""; + else + return fmt("", _arrived, _expected); +} diff --git a/hilti/toolchain/include/ast/all.h b/hilti/toolchain/include/ast/all.h index fa4318fd9..f1ea68ddb 100644 --- a/hilti/toolchain/include/ast/all.h +++ b/hilti/toolchain/include/ast/all.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include diff --git a/hilti/toolchain/include/ast/builder/expression.h b/hilti/toolchain/include/ast/builder/expression.h index c362118ce..44961129c 100644 --- a/hilti/toolchain/include/ast/builder/expression.h +++ b/hilti/toolchain/include/ast/builder/expression.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -358,4 +359,8 @@ inline auto scope(const Meta& m = Meta()) { return hilti::expression::Keyword(hilti::expression::keyword::Kind::Scope, m); } +inline auto barrier(uint64_t parties, const Meta& m = Meta()) { + return hilti::expression::Ctor(hilti::ctor::Barrier(parties), m); +} + } // namespace hilti::builder diff --git a/hilti/toolchain/include/ast/ctors/all.h b/hilti/toolchain/include/ast/ctors/all.h index 76cc7adc4..ca91f934c 100644 --- a/hilti/toolchain/include/ast/ctors/all.h +++ b/hilti/toolchain/include/ast/ctors/all.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include diff --git a/hilti/toolchain/include/ast/ctors/barrier.h b/hilti/toolchain/include/ast/ctors/barrier.h new file mode 100644 index 000000000..13d389a55 --- /dev/null +++ b/hilti/toolchain/include/ast/ctors/barrier.h @@ -0,0 +1,35 @@ +// Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details. + +#pragma once + +#include + +#include +#include + +namespace hilti::ctor { + +/** AST node for a barrier constructor. */ +class Barrier : public NodeBase, public hilti::trait::isCtor { +public: + Barrier(uint64_t parties, const Meta& m = Meta()) : NodeBase(nodes(type::Barrier(parties, m)), m) {} + Barrier(const Meta& m = Meta()) : NodeBase(nodes(type::Barrier(type::Wildcard(), m)), m) {} + + bool operator==(const Barrier& other) const { return false; } + + /** Implements `Ctor` interface. */ + const Type& type() const { return child(0); } + /** Implements `Ctor` interface. */ + bool isConstant() const { return true; } + /** Implements `Ctor` interface. */ + auto isLhs() const { return false; } + /** Implements `Ctor` interface. */ + auto isTemporary() const { return true; } + /** Implements `Ctor` interface. */ + auto isEqual(const Ctor& other) const { return node::isEqual(this, other); } + + /** Implements `Node` interface. */ + auto properties() const { return node::Properties{}; } +}; + +} // namespace hilti::ctor diff --git a/hilti/toolchain/include/ast/nodes.decl b/hilti/toolchain/include/ast/nodes.decl index ca5959b34..ac5ea37f0 100644 --- a/hilti/toolchain/include/ast/nodes.decl +++ b/hilti/toolchain/include/ast/nodes.decl @@ -24,6 +24,7 @@ hilti::type::enum_::Label : isNode hilti::type::tuple::Element : isNode hilti::ctor::Address : isCtor +hilti::ctor::Barrier : isCtor hilti::ctor::Bool : isCtor hilti::ctor::Bytes : isCtor hilti::ctor::Coerced : isCtor @@ -113,6 +114,7 @@ hilti::statement::Yield : isStatement hilti::type::Address : isType hilti::type::Any : isType hilti::type::Auto : isType +hilti::type::Barrier : isType hilti::type::Bool : isType hilti::type::Bytes : isType hilti::type::DocOnly : isType diff --git a/hilti/toolchain/include/ast/operators/all.h b/hilti/toolchain/include/ast/operators/all.h index db9001334..edf895a73 100644 --- a/hilti/toolchain/include/ast/operators/all.h +++ b/hilti/toolchain/include/ast/operators/all.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include diff --git a/hilti/toolchain/include/ast/operators/barrier.h b/hilti/toolchain/include/ast/operators/barrier.h new file mode 100644 index 000000000..84fc5c5f0 --- /dev/null +++ b/hilti/toolchain/include/ast/operators/barrier.h @@ -0,0 +1,75 @@ +// Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details. + +#pragma once + +#include +#include +#include + +namespace hilti::operator_ { + +STANDARD_KEYWORD_CTOR(barrier, Ctor, "barrier", type::Barrier(type::Wildcard()), + type::UnsignedInteger(type::Wildcard()), + "Creates a barrier that will wait for the given number of parties."); + +BEGIN_METHOD(barrier, Wait) + const auto& signature() const { + static auto _signature = Signature{.self = type::Barrier(type::Wildcard()), + .result = type::void_, + .id = "wait", + .args = {}, + .doc = R"( +Blocks the caller until the barrier is released by the expected number of +parties arriving. If the barrier is already released, it will return +immediately. If the barrier gets aborted before or during the wait, the method +will throw a ``BarrierAborted`` exception. +)"}; + return _signature; + } +END_METHOD + +BEGIN_METHOD(barrier, Arrive) + const auto& signature() const { + static auto _signature = Signature{.self = type::Barrier(type::Wildcard()), + .result = type::void_, + .id = "arrive", + .args = {}, + .doc = R"( +Signals a party's arrival at the barrier, potentially releasing it if +the expected number of parties have been seen now. This has no effect if +the barrier is already released or aborted. +)"}; + return _signature; + } +END_METHOD + +BEGIN_METHOD(barrier, ArriveAndWait) + const auto& signature() const { + static auto _signature = Signature{.self = type::Barrier(type::Wildcard()), + .result = type::void_, + .id = "arrive_and_wait", + .args = {}, + .doc = R"( +Convenience method combining a `arrive()` with an immediately following +`wait()`. +)"}; + return _signature; + } +END_METHOD + +BEGIN_METHOD(barrier, Abort) + const auto& signature() const { + static auto _signature = Signature{.self = type::Barrier(type::Wildcard()), + .result = type::void_, + .id = "abort", + .args = {}, + .doc = R"( +Aborts the barrier, causing any waiting parties to throw a +``BarrierAborted`` exception. This has no effect if the barrier is +already released or aborted. +)"}; + return _signature; + } +END_METHOD + +} // namespace hilti::operator_ diff --git a/hilti/toolchain/include/ast/types/all.h b/hilti/toolchain/include/ast/types/all.h index 56a6fe6e7..dc92cb634 100644 --- a/hilti/toolchain/include/ast/types/all.h +++ b/hilti/toolchain/include/ast/types/all.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/hilti/toolchain/include/ast/types/barrier.h b/hilti/toolchain/include/ast/types/barrier.h new file mode 100644 index 000000000..9f695dd7d --- /dev/null +++ b/hilti/toolchain/include/ast/types/barrier.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details. + +#pragma once + +#include +#include + +#include +#include + +namespace hilti::type { + +/** AST node for a `barrier` type. */ +class Barrier : public TypeBase, trait::isAllocable, trait::isParameterized { +public: + Barrier(uint64_t expected_parties, Meta m = Meta()) : TypeBase(std::move(m)), _parties(expected_parties) {} + Barrier(Wildcard /*unused*/, Meta m = Meta()) : TypeBase(std::move(m)), _wildcard(true) {} + + auto parties() const { return _parties; } + + bool operator==(const Barrier& other) const { return parties() == other.parties(); } + + /** Implements the `Type` interface. */ + auto isEqual(const Type& other) const { return node::isEqual(this, other); } + /** Implements the `Type` interface. */ + auto _isResolved(ResolvedState* rstate) const { return true; } + /** Implements the `Type` interface. */ + auto isWildcard() const { return _wildcard; } + /** Implements the `Type` interface. */ + std::vector typeParameters() const { return {Ctor(ctor::UnsignedInteger(parties(), 64))}; } + + /** Implements the `Node` interface. */ + auto properties() const { return node::Properties{{"parties", _parties}}; } + +private: + uint64_t _parties = 0; // number of parties expected to arrive at the barrier + bool _wildcard = false; +}; + +} // namespace hilti::type diff --git a/hilti/toolchain/src/compiler/codegen/ctors.cc b/hilti/toolchain/src/compiler/codegen/ctors.cc index 6c8ffce3c..57390c898 100644 --- a/hilti/toolchain/src/compiler/codegen/ctors.cc +++ b/hilti/toolchain/src/compiler/codegen/ctors.cc @@ -23,6 +23,12 @@ struct Visitor : hilti::visitor::PreOrder { result_t operator()(const ctor::Address& n) { return fmt("::hilti::rt::Address(\"%s\")", n.value()); } + result_t operator()(const ctor::Barrier& n) { + auto btype = n.type().as(); + assert(! btype.isWildcard()); + return fmt("::hilti::rt::Barrier(%u)", btype.parties()); + } + result_t operator()(const ctor::Bool& n) { return fmt("::hilti::rt::Bool(%s)", n.value() ? "true" : "false"); } result_t operator()(const ctor::Bytes& n) { return fmt("\"%s\"_b", util::escapeBytesForCxx(n.value())); } diff --git a/hilti/toolchain/src/compiler/codegen/operators.cc b/hilti/toolchain/src/compiler/codegen/operators.cc index 9ba32adbb..5096e5ed6 100644 --- a/hilti/toolchain/src/compiler/codegen/operators.cc +++ b/hilti/toolchain/src/compiler/codegen/operators.cc @@ -88,6 +88,33 @@ struct Visitor : hilti::visitor::PreOrder { result_t operator()(const operator_::address::Unequal& n) { return binary(n, "!="); } result_t operator()(const operator_::address::Family& n) { return fmt("%s.family()", op0(n)); } + // Barrier + + result_t operator()(const operator_::barrier::Ctor& n) { + auto args = tupleArguments(n, n.op1()); + return fmt("::hilti::rt::Barrier(%s)", args[0]); + } + + result_t operator()(const operator_::barrier::Wait& n) { + auto [self, args] = methodArguments(n); + return fmt("%s.wait()", self); + } + + result_t operator()(const operator_::barrier::Arrive& n) { + auto [self, args] = methodArguments(n); + return fmt("%s.arrive()", self); + } + + result_t operator()(const operator_::barrier::ArriveAndWait& n) { + auto [self, args] = methodArguments(n); + return fmt("%s.arrive_and_wait()", self); + } + + result_t operator()(const operator_::barrier::Abort& n) { + auto [self, args] = methodArguments(n); + return fmt("%s.abort()", self); + } + /// Bool result_t operator()(const operator_::bool_::Equal& n) { return binary(n, "=="); } diff --git a/hilti/toolchain/src/compiler/codegen/types.cc b/hilti/toolchain/src/compiler/codegen/types.cc index ebd40c02b..3b750f67d 100644 --- a/hilti/toolchain/src/compiler/codegen/types.cc +++ b/hilti/toolchain/src/compiler/codegen/types.cc @@ -363,6 +363,11 @@ struct VisitorStorage : hilti::visitor::PreOrder { result_t operator()(const type::Any& n) { return CxxTypes{.base_type = "::hilti::rt::any"}; } + result_t operator()(const type::Barrier& n) { + auto ctor = fmt("::hilti::rt::Barrier(%d)", n.parties()); + return CxxTypes{.base_type = "::hilti::rt::Barrier", .ctor = ctor, .default_ = ctor}; + } + result_t operator()(const type::Bool& n) { return CxxTypes{.base_type = "::hilti::rt::Bool"}; } result_t operator()(const type::Bytes& n) { return CxxTypes{.base_type = "::hilti::rt::Bytes"}; } @@ -754,6 +759,7 @@ struct VisitorTypeInfoPredefined : hilti::visitor::PreOrder, VisitorCtor> const Type& dst; bitmask style; + result_t operator()(const ctor::Barrier& c) { + if ( auto t = dst.tryAs(); t && c.type().isWildcard() ) + return ctor::Barrier(t->parties(), c.meta()); + + return {}; + } + result_t operator()(const ctor::Enum& c) { if ( dst.isA() && (style & CoercionStyle::ContextualConversion) ) return ctor::Bool(c.value().id() != ID("Undef"), c.meta()); diff --git a/hilti/toolchain/src/compiler/parser/parser.yy b/hilti/toolchain/src/compiler/parser/parser.yy index dd59af431..fdae5f06f 100644 --- a/hilti/toolchain/src/compiler/parser/parser.yy +++ b/hilti/toolchain/src/compiler/parser/parser.yy @@ -33,8 +33,8 @@ namespace hilti { namespace detail { class Parser; } } %verbose %glr-parser -%expect 113 -%expect-rr 207 +%expect 114 +%expect-rr 209 %{ @@ -97,6 +97,7 @@ static hilti::Type viewForType(hilti::Type t, hilti::Meta m) { %token ARROW "->" %token AUTO "auto" %token AT "at" +%token BARRIER "barrier" %token BEGIN_ "begin" %token BOOL "bool" %token BREAK "break" @@ -585,9 +586,11 @@ base_type_no_attrs | MAP type_param_begin '*' type_param_end { $$ = hilti::type::Map(hilti::type::Wildcard(), __loc__); } | MAP type_param_begin type ',' type type_param_end { $$ = hilti::type::Map(std::move($3), std::move($5), __loc__); } + | EXCEPTION { $$ = hilti::type::Exception(__loc__); } | EXCEPTION ':' type { $$ = hilti::type::Exception(std::move($3), __loc__); } + | BARRIER '(' CUINTEGER ')' { $$ = hilti::type::Barrier($3, __loc__); } | LIBRARY_TYPE '(' CSTRING ')' { $$ = hilti::type::Library(std::move($3), __loc__); } | tuple_type { $$ = std::move($1); } @@ -837,6 +840,8 @@ ctor_expr : INTERVAL '(' expr ')' { $$ = hilti::builder::namedCto | UINT32 '(' expr ')' { $$ = hilti::builder::namedCtor("uint32", { std::move($3) }, __loc__); } | UINT64 '(' expr ')' { $$ = hilti::builder::namedCtor("uint64", { std::move($3) }, __loc__); } | PORT '(' expr ',' expr ')' { $$ = hilti::builder::namedCtor("port", {std::move($3), std::move($5)}, __loc__); } + | BARRIER '(' expr ')' { $$ = hilti::builder::namedCtor("barrier", { std::move($3) }, __loc__); } + | BARRIER '(' ')' { $$ = hilti::expression::Ctor(hilti::ctor::Barrier(__loc__), __loc__); } ; tuple : '(' opt_tuple_elems1 ')' { $$ = hilti::ctor::Tuple(std::move($2), __loc__); } diff --git a/hilti/toolchain/src/compiler/parser/scanner.ll b/hilti/toolchain/src/compiler/parser/scanner.ll index dee8fbfad..a17310ebe 100644 --- a/hilti/toolchain/src/compiler/parser/scanner.ll +++ b/hilti/toolchain/src/compiler/parser/scanner.ll @@ -83,6 +83,7 @@ any return token::ANY; assert return token::ASSERT; assert-exception return token::ASSERT_EXCEPTION; auto return token::AUTO; +barrier return token::BARRIER; begin return token::BEGIN_; bool return token::BOOL; break return token::BREAK; diff --git a/hilti/toolchain/src/compiler/visitors/constant-folder.cc b/hilti/toolchain/src/compiler/visitors/constant-folder.cc index c812c376c..d519180bf 100644 --- a/hilti/toolchain/src/compiler/visitors/constant-folder.cc +++ b/hilti/toolchain/src/compiler/visitors/constant-folder.cc @@ -160,6 +160,12 @@ struct VisitorConstantFolder : public visitor::PreOrder, Vis return ctor::Real(-op->value(), p.node.meta()); } + result_t operator()(const operator_::barrier::Ctor& op, position_t p) { + return tryReplaceCtorExpression(op, p, [](const auto& ctor) { + return ctor::Barrier(ctor.value()); + }); + } + result_t operator()(const operator_::error::Ctor& op, position_t p) { return tryReplaceCtorExpression(op, p, [](const auto& ctor) { return ctor::Error(ctor.value()); }); } diff --git a/hilti/toolchain/src/compiler/visitors/printer.cc b/hilti/toolchain/src/compiler/visitors/printer.cc index 839dea78d..d52b7d885 100644 --- a/hilti/toolchain/src/compiler/visitors/printer.cc +++ b/hilti/toolchain/src/compiler/visitors/printer.cc @@ -11,6 +11,8 @@ #include #include +#include "ast/types/barrier.h" + using namespace hilti; using util::fmt; @@ -230,6 +232,13 @@ struct Visitor : visitor::PreOrder { void operator()(const ctor::Address& n) { out << n.value(); } + void operator()(const ctor::Barrier& n) { + if ( n.type().isWildcard() ) + out << "barrier()" << out.newline(); + else + out << "barrier(\"" << n.type().as().parties() << "\")"; + } + void operator()(const ctor::Bool& n) { out << (n.value() ? "True" : "False"); } void operator()(const ctor::Bytes& n) { out << "b\"" << util::escapeUTF8(n.value(), true) << '"'; } @@ -795,6 +804,13 @@ struct Visitor : visitor::PreOrder { void operator()(const type::Auto& n) { out << const_(n) << "auto"; } + void operator()(const type::Barrier& n) { + if ( n.isWildcard() ) + out << const_(n) << "barrier()"; + else + out << const_(n) << "barrier<" << n.parties() << '>'; + } + void operator()(const type::Bool& n) { out << const_(n) << "bool"; } void operator()(const type::Bytes& n) { out << const_(n) << "bytes"; } diff --git a/spicy/runtime/include/driver.h b/spicy/runtime/include/driver.h index dbcaa4c20..9fbb49d8b 100644 --- a/spicy/runtime/include/driver.h +++ b/spicy/runtime/include/driver.h @@ -70,6 +70,12 @@ class ParsingState { */ bool isFinished() const { return _done || _skip; } + /** + * Returns true if parsing has yielded due to waiting for a barrier + * release. If that's the case, the next resume will re-check the barrier. + */ + bool isWaitingAtBarrier() const { return _resumable && _resumable->atBarrier() && ! isFinished(); } + /** * Explicitly skips any remaining input. Further calls to `process()` and * `finish()` will be ignored. @@ -163,26 +169,35 @@ class ParsingStateForDriver : public ParsingState { * * @param id textual ID to associate with state for use in debug messages * + * @param reverse_id if the state is associated with one side of a + * connection, a textual ID that's associated with the state for the + * opposite direction + * @param cid if the state is associated with one side of a * connection, a textual ID representing that connection. * * @param driver driver owning this state */ - ParsingStateForDriver(ParsingType type, const Parser* parser, std::string id, std::optional cid, - std::optional context, Driver* driver) + ParsingStateForDriver(ParsingType type, const Parser* parser, std::string id, std::optional reverse_id, + std::optional cid, std::optional context, Driver* driver) : ParsingState(type, parser, std::move(context)), _id(std::move(std::move(id))), + _reverse_id(std::move(reverse_id)), _cid(std::move(std::move(cid))), _driver(driver) {} /** Returns the textual ID associated with the state. */ const auto& id() const { return _id; } + /** Returns the textual ID associated with the opposite direction, if any. */ + const auto& reverseId() const { return _reverse_id; } + protected: void debug(const std::string& msg) override; private: std::string _id; + std::optional _reverse_id; std::optional _cid; Driver* _driver; }; diff --git a/spicy/runtime/src/driver.cc b/spicy/runtime/src/driver.cc index 4bb899e03..b8d2335a8 100644 --- a/spicy/runtime/src/driver.cc +++ b/spicy/runtime/src/driver.cc @@ -327,7 +327,7 @@ driver::ParsingState::State driver::ParsingState::_process(size_t size, const ch return Done; } else { - if ( eod ) + if ( eod && ! _resumable->atBarrier() ) hilti::rt::internalError("parsing yielded for final data chunk"); return Continue; @@ -355,13 +355,14 @@ Result Driver::processPreBatchedInput(std::istream& in) { // Helper to add flows to the map. auto create_state = [&](driver::ParsingType type, const std::string& parser_name, const std::string& id, - std::optional cid, std::optional context) { + std::optional reverse_id, std::optional cid, + std::optional context) { if ( auto parser = lookupParser(parser_name) ) { if ( ! context ) context = (*parser)->createContext(); - auto x = flows.insert_or_assign(id, driver::ParsingStateForDriver(type, *parser, id, std::move(cid), - context, this)); + auto x = flows.insert_or_assign(id, driver::ParsingStateForDriver(type, *parser, id, std::move(reverse_id), + std::move(cid), context, this)); if ( x.second ) _total_flows++; @@ -373,6 +374,32 @@ Result Driver::processPreBatchedInput(std::istream& in) { } }; + auto process_flow_data = [&](const std::string& id, size_t size, const char* data) { + auto s = flows.find(id); + if ( s == flows.end() ) + return; + + try { + s->second.process(size, data); + } catch ( const hilti::rt::Exception& e ) { + std::cout << hilti::rt::fmt("error for ID %s: %s\n", id, e.what()); + } + + if ( const auto& rid = s->second.reverseId() ) { + auto r = flows.find(*rid); + if ( r == flows.end() || ! r->second.isWaitingAtBarrier() ) + return; + + try { + // Give the other side a chance to check if it's barrier has + // been released. + r->second.process(0, ""); + } catch ( const hilti::rt::Exception& e ) { + std::cout << hilti::rt::fmt("error for ID %s: %s\n", *rid, e.what()); + } + } + }; + while ( in.good() && ! in.eof() ) { std::string cmd; std::getline(in, cmd); @@ -400,7 +427,7 @@ Result Driver::processPreBatchedInput(std::istream& in) { return hilti::rt::result::Error(hilti::rt::fmt("unknown session type '%s'", m[2])); - create_state(type, parser_name, id, {}, {}); + create_state(type, parser_name, id, {}, {}, {}); } else if ( m[0] == "@begin-conn" ) { // @begin-conn @@ -433,12 +460,14 @@ Result Driver::processPreBatchedInput(std::istream& in) { std::optional context; - if ( auto [x, ctx] = create_state(type, orig_parser_name, orig_id, cid, context); x != flows.end() ) { + if ( auto [x, ctx] = create_state(type, orig_parser_name, orig_id, resp_id, cid, context); + x != flows.end() ) { orig_state = &x->second; context = std::move(ctx); } - if ( auto [x, ctx] = create_state(type, resp_parser_name, resp_id, cid, context); x != flows.end() ) + if ( auto [x, ctx] = create_state(type, resp_parser_name, resp_id, orig_id, cid, context); + x != flows.end() ) resp_state = &x->second; if ( ! (orig_state && resp_state) ) { @@ -470,14 +499,7 @@ Result Driver::processPreBatchedInput(std::istream& in) { if ( in.eof() || in.fail() ) return hilti::rt::result::Error("premature end of @data"); - auto s = flows.find(id); - if ( s != flows.end() ) { - try { - s->second.process(size, data); - } catch ( const hilti::rt::Exception& e ) { - std::cout << hilti::rt::fmt("error for ID %s: %s\n", id, e.what()); - } - } + process_flow_data(id, size, data); } else if ( m[0] == "@gap" ) { // @gap @@ -486,15 +508,7 @@ Result Driver::processPreBatchedInput(std::istream& in) { auto id = std::string(m[1]); auto size = std::stoul(std::string(m[2])); - - auto s = flows.find(id); - if ( s != flows.end() ) { - try { - s->second.process(size, nullptr); - } catch ( const hilti::rt::Exception& e ) { - std::cout << hilti::rt::fmt("error for ID %s: %s\n", id, e.what()); - } - } + process_flow_data(id, size, nullptr); } else if ( m[0] == "@end-flow" ) { // @end-flow diff --git a/spicy/toolchain/bin/spicy-dump/printer-json.cc b/spicy/toolchain/bin/spicy-dump/printer-json.cc index c112673b6..2f35a6b15 100644 --- a/spicy/toolchain/bin/spicy-dump/printer-json.cc +++ b/spicy/toolchain/bin/spicy-dump/printer-json.cc @@ -22,6 +22,7 @@ nlohmann::json JSONPrinter::convert(const hilti::rt::type_info::Value& v) { case TypeInfo::Undefined: throw RuntimeError("unhandled type"); case TypeInfo::Address: return type.address->get(v); case TypeInfo::Any: return ""; + case TypeInfo::Barrier: return ""; case TypeInfo::Bool: return type.bool_->get(v); case TypeInfo::Bytes: return to_string_for_print(type.bytes->get(v)); case TypeInfo::BytesIterator: return to_string(type.bytes_iterator->get(v)); diff --git a/spicy/toolchain/bin/spicy-dump/printer-text.cc b/spicy/toolchain/bin/spicy-dump/printer-text.cc index 6b01042cc..1a3a23b8d 100644 --- a/spicy/toolchain/bin/spicy-dump/printer-text.cc +++ b/spicy/toolchain/bin/spicy-dump/printer-text.cc @@ -15,6 +15,7 @@ void TextPrinter::print(const type_info::Value& v) { case TypeInfo::Undefined: throw RuntimeError("unhandled type"); case TypeInfo::Address: out() << type.address->get(v); break; case TypeInfo::Any: out() << ""; break; + case TypeInfo::Barrier: out() << ""; break; case TypeInfo::Bool: out() << (type.bool_->get(v) ? "True" : "False"); break; case TypeInfo::Bytes: out() << to_string_for_print(type.bytes->get(v)); break; case TypeInfo::BytesIterator: out() << to_string(type.bytes_iterator->get(v)); break; diff --git a/spicy/toolchain/src/compiler/parser/parser.yy b/spicy/toolchain/src/compiler/parser/parser.yy index 0bb989f60..cb1dc8626 100644 --- a/spicy/toolchain/src/compiler/parser/parser.yy +++ b/spicy/toolchain/src/compiler/parser/parser.yy @@ -34,8 +34,8 @@ namespace spicy { namespace detail { class Parser; } } %verbose %glr-parser -%expect 125 -%expect-rr 164 +%expect 128 +%expect-rr 166 %{ @@ -134,6 +134,7 @@ static std::vector _docs; %token ANY %token ARROW %token AUTO +%token BARRIER %token BITFIELD %token BEGIN_ %token BOOL @@ -620,6 +621,7 @@ base_type_no_ref | SINK { $$ = spicy::type::Sink(__loc__); } + | BARRIER '(' CUINTEGER ')' { $$ = hilti::type::Barrier($3, __loc__); } | LIBRARY_TYPE '(' CSTRING ')' { $$ = hilti::type::Library(std::move($3), __loc__); } | tuple_type { $$ = std::move($1); } @@ -1029,6 +1031,8 @@ ctor_expr : INTERVAL '(' expr ')' { $$ = hilti::builder::namedCto | UINT32 '(' expr ')' { $$ = hilti::builder::namedCtor("uint32", { std::move($3) }, __loc__); } | UINT64 '(' expr ')' { $$ = hilti::builder::namedCtor("uint64", { std::move($3) }, __loc__); } | PORT '(' expr ',' expr ')' { $$ = hilti::builder::namedCtor("port", {std::move($3), std::move($5)}, __loc__); } + | BARRIER '(' expr ')' { $$ = hilti::builder::namedCtor("barrier", { std::move($3) }, __loc__); } + | BARRIER '(' ')' { $$ = hilti::expression::Ctor(hilti::ctor::Barrier(__loc__), __loc__); } ; tuple : '(' opt_tuple_elems1 ')' { $$ = hilti::ctor::Tuple(std::move($2), __loc__); } diff --git a/spicy/toolchain/src/compiler/parser/scanner.ll b/spicy/toolchain/src/compiler/parser/scanner.ll index 6f34c2d28..812a145fd 100644 --- a/spicy/toolchain/src/compiler/parser/scanner.ll +++ b/spicy/toolchain/src/compiler/parser/scanner.ll @@ -101,6 +101,7 @@ any return token::ANY; assert return token::ASSERT; assert-exception return token::ASSERT_EXCEPTION; attribute return token::ATTRIBUTE; +barrier return token::BARRIER; begin return token::BEGIN_; bitfield return token::BITFIELD; bool return token::BOOL; diff --git a/tests/Baseline/hilti.types.barrier.abort/output b/tests/Baseline/hilti.types.barrier.abort/output new file mode 100644 index 000000000..3d0b9d406 --- /dev/null +++ b/tests/Baseline/hilti.types.barrier.abort/output @@ -0,0 +1,8 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +-- starting rx +in x: +-- starting ry +in y: +-- abort in ry +-- resuming rx +-- abort in rx diff --git a/tests/Baseline/hilti.types.barrier.ctors/output b/tests/Baseline/hilti.types.barrier.ctors/output new file mode 100644 index 000000000..9d904ea3a --- /dev/null +++ b/tests/Baseline/hilti.types.barrier.ctors/output @@ -0,0 +1,4 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +b1: +b2: +b3: diff --git a/tests/Baseline/hilti.types.barrier.ops/output b/tests/Baseline/hilti.types.barrier.ops/output new file mode 100644 index 000000000..abb697526 --- /dev/null +++ b/tests/Baseline/hilti.types.barrier.ops/output @@ -0,0 +1,11 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +1 x: +1 y: +2 y: +3 y: +-- resuming rx +2 x: +3 x: +4 x: +-- resuming ry +4 y: diff --git a/tests/Baseline/spicy.types.barrier.conn-abort/output b/tests/Baseline/spicy.types.barrier.conn-abort/output new file mode 100644 index 000000000..5ab054ebd --- /dev/null +++ b/tests/Baseline/spicy.types.barrier.conn-abort/output @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +error for ID orig: broken barrier (<...>/conn-abort.spicy:23:8) +%done resp, [$sync=] diff --git a/tests/Baseline/spicy.types.barrier.conn/output b/tests/Baseline/spicy.types.barrier.conn/output new file mode 100644 index 000000000..feae8c158 --- /dev/null +++ b/tests/Baseline/spicy.types.barrier.conn/output @@ -0,0 +1,3 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +[$sync=, $result=b"ab1234cdef"] +[$sync=, $result=b"ab1234cdef56"] diff --git a/tests/hilti/codegen/type-info.hlt b/tests/hilti/codegen/type-info.hlt index 9a44575b3..0597c0b8d 100644 --- a/tests/hilti/codegen/type-info.hlt +++ b/tests/hilti/codegen/type-info.hlt @@ -26,6 +26,7 @@ type A_Union = union { public type TestTypes = struct { addr ad; any an; + barrier(42) ba; bool bo; bytes by; iterator bi; @@ -101,6 +102,7 @@ public function tuple, strong_ref, TypeInfo> ma local TestTypes x = [ $ad = 1.2.3.4, $an = "any", + $ba = barrier(), $bo = True, $by = b"bytes", $bi = begin(Bytes), @@ -209,7 +211,7 @@ void __check_eq(const T& x, const U& y, std::string loc) { // "TypesInit". struct VisitorTypesInit { std::set seen; - static inline const int ExpectedVisitorsSeen = 40; // all (43) minus void and function and MatchState (which comes as struct) + static inline const int ExpectedVisitorsSeen = 41; // all (44) minus void and function and MatchState (which comes as struct) // Helper for checking content of a struct of type "S". All our instances // of "S" have the same values. @@ -246,6 +248,11 @@ struct VisitorTypesInit { SEEN(); break; } + case TypeInfo::Barrier: { + SEEN(); + CHECK_EQ(std::string(type.barrier->get(v)), ""); + break; + } case TypeInfo::Bool: { SEEN(); CHECK_EQ(type.bool_->get(v), true); diff --git a/tests/hilti/types/barrier/abort.hlt b/tests/hilti/types/barrier/abort.hlt new file mode 100644 index 000000000..1d74f6744 --- /dev/null +++ b/tests/hilti/types/barrier/abort.hlt @@ -0,0 +1,56 @@ +# @TEST-EXEC: hiltic -P -o Test.h %INPUT +# @TEST-EXEC: hiltic -j %INPUT driver.cc >output +# @TEST-EXEC: btest-diff output +# +# @TEST-DOC: Confirms exception behavior when aborting a barrier. + +module Test { + +import hilti; + +global b = barrier(2); + +function extern void x() { + hilti::print("in x: %s" % b); + b.arrive_and_wait(); # blocks, and then throws + assert False; # won't arrive here +} + +function extern void y() { + hilti::print("in y: %s" % b); + b.abort(); + b.arrive_and_wait(); # throws + assert False; # won't arrive here +} + +} + +@TEST-START-FILE driver.cc + +#include "Test.h" + +extern "C" int HILTI_EXPORT hilti_main() { + std::cout << "-- starting rx" << std::endl; + auto rx = hlt::Test::x(); + assert(rx); + + try { + std::cout << "-- starting ry" << std::endl; + auto ry = hlt::Test::y(); + assert(false); + } catch ( hilti::rt::BarrierAborted& e ) { + std::cout << "-- abort in ry" << std::endl; + } + + std::cout << "-- resuming rx" << std::endl; + + try { + rx.resume(); + } catch ( hilti::rt::BarrierAborted& e ) { + std::cout << "-- abort in rx" << std::endl; + } + + return 0; +} + +@TEST-END-FILE diff --git a/tests/hilti/types/barrier/ctors.hlt b/tests/hilti/types/barrier/ctors.hlt new file mode 100644 index 000000000..543b54762 --- /dev/null +++ b/tests/hilti/types/barrier/ctors.hlt @@ -0,0 +1,18 @@ +# @TEST-EXEC: hiltic -j %INPUT >output +# @TEST-EXEC: btest-diff output +# +# @TEST-DOC: Check barrier construction. + +module Test { + +import hilti; + +global b1 = barrier(1); +global barrier(2) b2; +global barrier(3) b3 = barrier(); + +hilti::print("b1: %s" % b1); +hilti::print("b2: %s" % b2); +hilti::print("b3: %s" % b3); + +} diff --git a/tests/hilti/types/barrier/ops.hlt b/tests/hilti/types/barrier/ops.hlt new file mode 100644 index 000000000..ea180315e --- /dev/null +++ b/tests/hilti/types/barrier/ops.hlt @@ -0,0 +1,64 @@ +# @TEST-EXEC: hiltic -P -o Test.h %INPUT +# @TEST-EXEC: hiltic -j %INPUT driver.cc >output +# @TEST-EXEC: btest-diff output +# +# @TEST-DOC: Check barrier operation. + +module Test { + +import hilti; + +global b1 = barrier(2); +global b2 = barrier(2); + +function extern void x() { + hilti::print("1 x: %s" % b1); + b1.arrive_and_wait(); # blocks + hilti::print("2 x: %s" % b1); + + hilti::print("3 x: %s" % b2); + b2.arrive_and_wait(); # does not block + hilti::print("4 x: %s" % b2); +} + +function extern void y() { + hilti::print("1 y: %s" % b1); + b1.arrive(); + b1.wait(); # does not block + hilti::print("2 y: %s" % b1); + + hilti::print("3 y: %s" % b2); + b2.arrive(); + b2.wait(); # blocks + hilti::print("4 y: %s" % b2); +} + +} + +@TEST-START-FILE driver.cc + +#include "Test.h" + +extern "C" int HILTI_EXPORT hilti_main() { + auto rx = hlt::Test::x(); + assert(rx); + + auto ry = hlt::Test::y(); + assert(ry); + + while ( ! (rx && ry) ) { + if ( ! rx ) { + std::cout << "-- resuming rx" << std::endl; + rx.resume(); + } + + if ( ! ry ) { + std::cout << "-- resuming ry" << std::endl; + ry.resume(); + } + } + + return 0; +} + +@TEST-END-FILE diff --git a/tests/spicy/types/barrier/conn-abort.spicy b/tests/spicy/types/barrier/conn-abort.spicy new file mode 100644 index 000000000..e812cbc1b --- /dev/null +++ b/tests/spicy/types/barrier/conn-abort.spicy @@ -0,0 +1,40 @@ +# @TEST-EXEC: spicyc -d -j -o test.hlto %INPUT +# @TEST-EXEC: spicy-driver -F test.dat test.hlto >output +# @TEST-EXEC: btest-diff output +# +# @TEST-DOC: Craft a typical barrier scenario where the two sides of a connection need to wait for each other. + +module Test; + +type Context = struct { + sync: barrier(2); +}; + +public type Orig = unit { + %context = Context; + x: b"ab" { self.context().sync.arrive_and_wait(); } + y: b"cd"; + on %done { print "%done orig", self.context(); } +}; + +public type Resp = unit { + %context = Context; + x: b"12" { self.context().sync.abort(); } + y: b"34"; + on %done { print "%done resp", self.context(); } +}; + +@TEST-START-FILE test.dat +!spicy-batch v2 +@begin-conn conn stream orig Test::Orig resp Test::Resp +@data orig 2 +ab +@data orig 2 +cd +@data resp 2 +12 +@data resp 2 +34 +@end-flow orig +@end-flow resp +@TEST-END-FILE diff --git a/tests/spicy/types/barrier/conn.spicy b/tests/spicy/types/barrier/conn.spicy new file mode 100644 index 000000000..be716634e --- /dev/null +++ b/tests/spicy/types/barrier/conn.spicy @@ -0,0 +1,51 @@ +# @TEST-EXEC: spicyc -d -j -o test.hlto %INPUT +# @TEST-EXEC: spicy-driver -F test.dat test.hlto >output +# @TEST-EXEC: btest-diff output +# +# @TEST-DOC: Craft a typical barrier scenario where the two sides of a connection need to wait for each other. + +module Test; + +type Context = struct { + sync: barrier(2); + result: bytes; +}; + +public type Orig = unit { + %context = Context; + + x: b"ab" { self.context().result += $$; self.context().sync.arrive_and_wait(); } + y: b"cd" { self.context().result += $$; } + z: b"ef" { self.context().result += $$; } + + on %done { print self.context(); } +}; + +public type Resp = unit { + %context = Context; + + x: b"12" { self.context().result += $$; } + y: b"34" { self.context().result += $$; self.context().sync.arrive_and_wait(); } + z: b"56" { self.context().result += $$; } + + on %done { print self.context(); } +}; + +@TEST-START-FILE test.dat +!spicy-batch v2 +@begin-conn conn stream orig Test::Orig resp Test::Resp +@data orig 2 +ab +@data orig 2 +cd +@data orig 2 +ef +@data resp 2 +12 +@data resp 2 +34 +@data resp 2 +56 +@end-flow orig +@end-flow resp +@TEST-END-FILE