summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/base/SConscript2
-rw-r--r--src/base/fiber.cc148
-rw-r--r--src/base/fiber.hh106
-rw-r--r--src/base/fibertest.cc140
4 files changed, 396 insertions, 0 deletions
diff --git a/src/base/SConscript b/src/base/SConscript
index a90b78486..b3205a6bb 100644
--- a/src/base/SConscript
+++ b/src/base/SConscript
@@ -46,6 +46,8 @@ if env['USE_FENV']:
Source('fenv.c')
if env['USE_PNG']:
Source('pngwriter.cc')
+Source('fiber.cc')
+GTest('fibertest', 'fibertest.cc', 'fiber.cc')
Source('framebuffer.cc')
Source('hostinfo.cc')
Source('inet.cc')
diff --git a/src/base/fiber.cc b/src/base/fiber.cc
new file mode 100644
index 000000000..f10f1fbfd
--- /dev/null
+++ b/src/base/fiber.cc
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018 Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors: Gabe Black
+ */
+
+#include "base/fiber.hh"
+
+#include <cerrno>
+#include <cstring>
+
+#include "base/logging.hh"
+
+using namespace std;
+
+namespace
+{
+
+/*
+ * The PrimaryFiber class is a special case that attaches to the currently
+ * executing context. That makes handling the "primary" fiber, aka the one
+ * which most of gem5 is running under, no different than other Fibers.
+ */
+class PrimaryFiber : public Fiber
+{
+ public:
+ PrimaryFiber() : Fiber(nullptr, 0) { setStarted(); }
+ void main() { panic("PrimaryFiber main executed.\n"); }
+};
+
+PrimaryFiber _primaryFiber;
+
+// A pointer to whatever the currently executing Fiber is.
+Fiber *_currentFiber = &_primaryFiber;
+
+// A pointer to the Fiber which is currently being started/initialized.
+Fiber *startingFiber = nullptr;
+
+} // anonymous namespace
+
+void
+Fiber::entryTrampoline()
+{
+ startingFiber->start();
+}
+
+Fiber::Fiber(size_t stack_size) :
+ link(primaryFiber()),
+ stack(stack_size ? new uint8_t[stack_size] : nullptr),
+ stackSize(stack_size), started(false), _finished(false)
+{}
+
+Fiber::Fiber(Fiber *link, size_t stack_size) :
+ link(link), stack(stack_size ? new uint8_t[stack_size] : nullptr),
+ stackSize(stack_size), started(false), _finished(false)
+{}
+
+Fiber::~Fiber()
+{
+ panic_if(stack && _currentFiber == this, "Fiber stack is in use.");
+ delete [] stack;
+}
+
+void
+Fiber::createContext()
+{
+ // Set up a context for the new fiber, starting it in the trampoline.
+ getcontext(&ctx);
+ ctx.uc_stack.ss_sp = stack;
+ ctx.uc_stack.ss_size = stackSize;
+ ctx.uc_link = nullptr;
+ makecontext(&ctx, &entryTrampoline, 0);
+
+ // Swap to the new context so it can enter its start() function. It
+ // will then swap itself back out and return here.
+ startingFiber = this;
+ panic_if(!_currentFiber, "No active Fiber object.");
+ swapcontext(&_currentFiber->ctx, &ctx);
+
+ // The new context is now ready and about to call main().
+}
+
+void
+Fiber::start()
+{
+ // Avoid a dangling pointer.
+ startingFiber = nullptr;
+
+ setStarted();
+
+ // Swap back to the parent context which is still considered "current",
+ // now that we're ready to go.
+ int ret M5_VAR_USED = swapcontext(&ctx, &_currentFiber->ctx);
+ panic_if(ret == -1, strerror(errno));
+
+ // Call main() when we're been reactivated for the first time.
+ main();
+
+ // main has returned, so this Fiber has finished. Switch to the "link"
+ // Fiber.
+ _finished = true;
+ link->run();
+}
+
+void
+Fiber::run()
+{
+ panic_if(_finished, "Fiber has already run to completion.");
+
+ // If we're already running this fiber, we're done.
+ if (_currentFiber == this)
+ return;
+
+ if (!started)
+ createContext();
+
+ // Switch out of the current Fiber's context and this one's in.
+ Fiber *prev = _currentFiber;
+ Fiber *next = this;
+ _currentFiber = next;
+ swapcontext(&prev->ctx, &next->ctx);
+}
+
+Fiber *Fiber::currentFiber() { return _currentFiber; }
+Fiber *Fiber::primaryFiber() { return &_primaryFiber; }
diff --git a/src/base/fiber.hh b/src/base/fiber.hh
new file mode 100644
index 000000000..b9f0683a5
--- /dev/null
+++ b/src/base/fiber.hh
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2018 Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors: Gabe Black
+ */
+
+#ifndef __BASE_FIBER_HH__
+#define __BASE_FIBER_HH__
+
+#include <ucontext.h>
+
+#include <cstddef>
+#include <cstdint>
+
+/**
+ * This class represents a fiber, which is a light weight sort of thread which
+ * is cooperatively scheduled and runs sequentially with other fibers, swapping
+ * in and out of a single actual thread of execution.
+ *
+ * To define your own threads, create a subclass of Fiber and override its
+ * main() function to do what you want your fiber to do. You can start it by
+ * calling its run() method which will stop your execution and start the other
+ * fiber in your place.
+ *
+ * If your main() function ends, that fiber will automatically switch to either
+ * the primary fiber, or to a particular fiber you specified at construction
+ * time, and your fiber is considered finished.
+ */
+
+class Fiber
+{
+ public:
+ const static size_t DefaultStackSize = 0x50000;
+
+ /// stack_size is the size of the stack available to this fiber.
+ /// link points to another fiber which will start executing when this
+ /// fiber's main function returns.
+ Fiber(size_t stack_size=DefaultStackSize);
+ Fiber(Fiber *link, size_t stack_size=DefaultStackSize);
+
+ virtual ~Fiber();
+
+ /// Start executing the fiber represented by this object. This function
+ /// will "return" when the current fiber is switched back to later on.
+ void run();
+
+ /// Returns whether the "main" function of this fiber has finished.
+ ///
+ bool finished() const { return _finished; };
+
+ /// Get a pointer to the current running Fiber.
+ ///
+ static Fiber *currentFiber();
+ /// Get a pointer to the primary Fiber.
+ /// This Fiber represents the thread of execution started by the OS, and
+ /// which has a Fiber attached to it after the fact.
+ static Fiber *primaryFiber();
+
+ protected:
+ /// This method is called when this fiber is first run. Override it to
+ /// give your fiber something to do. When main returns, the fiber will
+ /// mark itself as finished and switch to its link fiber.
+ virtual void main() = 0;
+
+ void setStarted() { started = true; }
+
+ private:
+ static void entryTrampoline();
+ void start();
+
+ ucontext_t ctx;
+ Fiber *link;
+
+ // The stack for this context, or a nullptr if allocated elsewhere.
+ uint8_t *stack;
+ size_t stackSize;
+
+ bool started;
+ bool _finished;
+ void createContext();
+};
+
+#endif // __BASE_FIBER_HH__
diff --git a/src/base/fibertest.cc b/src/base/fibertest.cc
new file mode 100644
index 000000000..5ed13d425
--- /dev/null
+++ b/src/base/fibertest.cc
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2018 Google, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors: Gabe Black
+ */
+
+#include <gtest/gtest.h>
+
+#include <initializer_list>
+#include <iostream>
+#include <vector>
+
+#include "base/fiber.hh"
+
+class TestFiber : public Fiber
+{
+ public:
+ const char *name;
+ std::vector<Fiber *> next;
+
+ TestFiber(const char *name, std::initializer_list<Fiber *> l);
+
+ void checkExpected();
+ void main();
+};
+
+extern TestFiber a;
+extern TestFiber b;
+extern TestFiber c;
+
+TestFiber a("A", { &b, &a, Fiber::primaryFiber(), &b, &c });
+TestFiber b("B", { &a, &c });
+TestFiber c("C", { &a, Fiber::primaryFiber(), Fiber::primaryFiber() });
+
+std::vector<TestFiber *>::iterator expectedIt;
+std::vector<TestFiber *> expected({
+ &a, &b, &a, &a, /* main Fiber, */
+ &a, &b, &c, &a, &c,
+ /* main Fiber, */ &c, &c
+});
+
+TestFiber::TestFiber(
+ const char *name, std::initializer_list<Fiber *> l) :
+ name(name), next(l)
+{}
+
+void
+TestFiber::checkExpected()
+{
+ ASSERT_NE(expectedIt, expected.end());
+ TestFiber *e = *expectedIt++;
+ EXPECT_EQ(e, this) << "Expected " << e->name << ", got " << name;
+}
+
+void
+TestFiber::main()
+{
+ checkExpected();
+ for (auto &n : next) {
+ n->run();
+ checkExpected();
+ }
+}
+
+TEST(Fiber, Switching)
+{
+ expectedIt = expected.begin();
+
+ a.run();
+ EXPECT_EQ(expectedIt - expected.begin(), 4);
+
+ a.run();
+ EXPECT_EQ(expectedIt - expected.begin(), 9);
+
+ c.run();
+ EXPECT_EQ(expectedIt - expected.begin(), 10);
+
+ EXPECT_FALSE(a.finished());
+ EXPECT_FALSE(b.finished());
+ EXPECT_FALSE(c.finished());
+
+ c.run();
+ EXPECT_EQ(expected.end(), expectedIt) <<
+ "Didn't exactly use up the expected Fiber sequence";
+
+ EXPECT_TRUE(c.finished());
+}
+
+int currentIndex = 0;
+
+class LinkedFiber : public Fiber
+{
+ public:
+ const int index;
+ LinkedFiber(Fiber *link, int index) : Fiber(link), index(index) {}
+
+ void
+ main()
+ {
+ EXPECT_EQ(currentIndex, index);
+ currentIndex++;
+ }
+};
+
+TEST(Fiber, Linked)
+{
+ currentIndex = 0;
+
+ LinkedFiber lf3(Fiber::primaryFiber(), 3);
+ LinkedFiber lf2(&lf3, 2);
+ LinkedFiber lf1(&lf2, 1);
+ LinkedFiber lf0(&lf1, 0);
+
+ lf0.run();
+
+ EXPECT_EQ(currentIndex, 4);
+}