#gcc #assembly #c 14 #setjmp
#gcc #сборка #c 14 #setjmp
Вопрос:
Почему происходит сбой следующего кода в Windows 10? Он застревает yield()
в setjmp()
вызове. Какие правила я нарушил? Я думаю, что я никогда не возвращаюсь из функций, где setjmp()
вызывается. Код отлично работает под linux / amd64.
class coroutine
{
jmp_buf env_in_;
jmp_buf env_out_;
::std::function<void()> f_;
bool running_;
bool terminated_;
::std::unique_ptr<char[]> stack_;
char* const stack_top_;
public:
explicit coroutine(::std::size_t const N = 128 * 1024) :
running_{false},
terminated_{true},
stack_(new char[N]),
stack_top_(stack_.get() N)
{
}
template <typename F>
explicit coroutine(::std::size_t const N, Famp;amp; f) :
coroutine(N)
{
assign(::std::forward<F>(f));
}
auto terminated() const noexcept
{
return terminated_;
}
template <typename F>
void assign(Famp;amp; f)
{
running_ = terminated_ = false;
f_ = [this, f = ::std::forward<F>(f)]() mutable
{
// stack switch
#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64)
asm volatile(
"movq %%rsp, %0"
:
: "rm" (stack_top_)
: "rsp"
);
#elif defined(i386) || defined(__i386) || defined(__i386__)
asm volatile(
"movl %%esp, %0"
:
: "rm" (stack_top_)
: "esp"
);
#else
#error "can't switch stack frame"
#endif
f(*this);
running_ = false;
terminated_ = true;
yield();
};
}
void yield() noexcept
{
if (setjmp(env_out_))
{
return;
}
else
{
longjmp(env_in_, 1);
}
}
void resume() noexcept
{
if (setjmp(env_in_))
{
return;
}
else if (running_)
{
longjmp(env_out_, 1);
}
else
{
running_ = true;
f_();
}
}
};
Вот тестовая программа:
#include <iostream>
#include "coroutine.hpp"
struct A
{
~A()
{
::std::cout << "destroyed" << ::std::endl;
}
};
int main()
{
coroutine c(1024 * 1024);
c.assign([](coroutineamp; c)
{
A a;
for (int i{}; i != 3; i)
{
::std::cout << i << ::std::endl;
c.yield();
}
}
);
while (!c.terminated())
{
c.resume();
}
return 0;
}
Комментарии:
1. В синтаксисе At amp; t операнды перевернуты, поэтому вы фактически не переключаете стек, а сохраняете указатель стека. Это, вероятно, не вызывает ошибку. Я не думаю, что добавление указателя стека в clobber также имеет смысл, поскольку это может дать указание компилятору сохранить и восстановить его, отрицая ваш переключатель (даже если вы делаете это в правильном направлении). Также обратите внимание, что это
new char[]
может привести к неправильному выравниванию вашего стека.2. @Jester Ваша точка зрения была правильной, но проблема сохраняется. Могу ли я изменить вопрос? Разве new не возвращает указатель, выровненный по каждому выравниванию?
3. @Jester Сглаживание указателя стека не имеет никакого эффекта ( gcc.gnu.org/bugzilla/show_bug.cgi?id=52813 ). OTOH, переключение контекстов вызывает вопрос обо всех других регистрах. Возможно, вместо этого все они должны быть заблокированы (вместе с памятью и флагами).
4. Именно, в этом и заключалась проблема. Другие регистры не были восстановлены. Пользовательский setjmp / longjmp решил это.