1 // Copyright 2019 The Marl Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Minimal assembly implementations of fiber context switching for Unix-based
16 // platforms.
17 //
18 // Note: Unlike makecontext, swapcontext or the Windows fiber APIs, these
19 // assembly implementations *do not* save or restore signal masks,
20 // floating-point control or status registers, FS and GS segment registers,
21 // thread-local storage state nor any SIMD registers. This should not be a
22 // problem as the marl scheduler requires fibers to be executed on the same
23 // thread throughout their lifetime.
24
25 #if defined(__x86_64__)
26 #include "osfiber_asm_x64.h"
27 #elif defined(__i386__)
28 #include "osfiber_asm_x86.h"
29 #elif defined(__aarch64__)
30 #include "osfiber_asm_aarch64.h"
31 #elif defined(__arm__)
32 #include "osfiber_asm_arm.h"
33 #elif defined(__powerpc64__)
34 #include "osfiber_asm_ppc64.h"
35 #elif defined(__mips__) && _MIPS_SIM == _ABI64
36 #include "osfiber_asm_mips64.h"
37 #else
38 #error "Unsupported target"
39 #endif
40
41 #include "marl/export.h"
42 #include "marl/memory.h"
43
44 #include <functional>
45 #include <memory>
46
47 extern "C" {
48
49 MARL_EXPORT
50 extern void marl_fiber_set_target(marl_fiber_context*,
51 void* stack,
52 uint32_t stack_size,
53 void (*target)(void*),
54 void* arg);
55 MARL_EXPORT
56 extern void marl_fiber_swap(marl_fiber_context* from,
57 const marl_fiber_context* to);
58
59 } // extern "C"
60
61 namespace marl {
62
63 class OSFiber {
64 public:
65 inline OSFiber(Allocator*);
66 inline ~OSFiber();
67
68 // createFiberFromCurrentThread() returns a fiber created from the current
69 // thread.
70 MARL_NO_EXPORT static inline Allocator::unique_ptr<OSFiber>
71 createFiberFromCurrentThread(Allocator* allocator);
72
73 // createFiber() returns a new fiber with the given stack size that will
74 // call func when switched to. func() must end by switching back to another
75 // fiber, and must not return.
76 MARL_NO_EXPORT static inline Allocator::unique_ptr<OSFiber> createFiber(
77 Allocator* allocator,
78 size_t stackSize,
79 const std::function<void()>& func);
80
81 // switchTo() immediately switches execution to the given fiber.
82 // switchTo() must be called on the currently executing fiber.
83 MARL_NO_EXPORT inline void switchTo(OSFiber*);
84
85 private:
86 MARL_NO_EXPORT
87 static inline void run(OSFiber* self);
88
89 Allocator* allocator;
90 marl_fiber_context context;
91 std::function<void()> target;
92 Allocation stack;
93 };
94
OSFiber(Allocator * allocator)95 OSFiber::OSFiber(Allocator* allocator) : allocator(allocator) {}
96
~OSFiber()97 OSFiber::~OSFiber() {
98 if (stack.ptr != nullptr) {
99 allocator->free(stack);
100 }
101 }
102
createFiberFromCurrentThread(Allocator * allocator)103 Allocator::unique_ptr<OSFiber> OSFiber::createFiberFromCurrentThread(
104 Allocator* allocator) {
105 auto out = allocator->make_unique<OSFiber>(allocator);
106 out->context = {};
107 return out;
108 }
109
createFiber(Allocator * allocator,size_t stackSize,const std::function<void ()> & func)110 Allocator::unique_ptr<OSFiber> OSFiber::createFiber(
111 Allocator* allocator,
112 size_t stackSize,
113 const std::function<void()>& func) {
114 Allocation::Request request;
115 request.size = stackSize;
116 request.alignment = 16;
117 request.usage = Allocation::Usage::Stack;
118 #if MARL_USE_FIBER_STACK_GUARDS
119 request.useGuards = true;
120 #endif
121
122 auto out = allocator->make_unique<OSFiber>(allocator);
123 out->context = {};
124 out->target = func;
125 out->stack = allocator->allocate(request);
126 marl_fiber_set_target(
127 &out->context, out->stack.ptr, static_cast<uint32_t>(stackSize),
128 reinterpret_cast<void (*)(void*)>(&OSFiber::run), out.get());
129 return out;
130 }
131
run(OSFiber * self)132 void OSFiber::run(OSFiber* self) {
133 self->target();
134 }
135
switchTo(OSFiber * fiber)136 void OSFiber::switchTo(OSFiber* fiber) {
137 marl_fiber_swap(&context, &fiber->context);
138 }
139
140 } // namespace marl
141