<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nomota</title>
    <description>The latest articles on DEV Community by Nomota (@nomota_062936).</description>
    <link>https://dev.to/nomota_062936</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3817706%2F3315f4b5-196b-4c37-92a2-2cfdec77e114.jpg</url>
      <title>DEV Community: Nomota</title>
      <link>https://dev.to/nomota_062936</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nomota_062936"/>
    <language>en</language>
    <item>
      <title>Writing a fiber coroutine in C/C3</title>
      <dc:creator>Nomota</dc:creator>
      <pubDate>Wed, 11 Mar 2026 04:40:06 +0000</pubDate>
      <link>https://dev.to/nomota_062936/writing-a-fiber-coroutine-in-cc3-472</link>
      <guid>https://dev.to/nomota_062936/writing-a-fiber-coroutine-in-cc3-472</guid>
      <description>&lt;p&gt;I love Go language because of it's simplicity AND the &lt;strong&gt;goroutine&lt;/strong&gt;. I always thinking of having similar thing in C or C3 language.&lt;/p&gt;

&lt;p&gt;D language provides a very lightweight simple-minded coroutine, in the name of Fiber, very clean API. Recently I also get interested in C3 language. A modern successor of C language, cleaner, safer, stronger. So I set out to implement Fiber for C3 language.&lt;/p&gt;

&lt;p&gt;There are 4 different way of implementing Fiber coroutine. (1) Calling assembly code for context switching, (2) Using &lt;code&gt;ucontext&lt;/code&gt; library on POSIX systems, (3) Using &lt;code&gt;sigsetjmp/siglongjmp&lt;/code&gt; on POSIX, (4) Using native &lt;code&gt;Fiber&lt;/code&gt; on windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fiber coroutine using Assembly
&lt;/h3&gt;

&lt;p&gt;Calling assembly code is extremely lightweight, but it is not very easy to maintain assembly codes for all architectures. For my case, I decided to cover only X86_64 and AARCH64, because they are most frequent platforms.&lt;/p&gt;

&lt;p&gt;Another issue with C3/inline assembly is that C3's assembly coverage is limited and it's not easy to express required inline assembly code. Luckily enough I found a workaround. &lt;strong&gt;Converting the assembly code into a hex array of binary data, and then call that memory area as like a normal function call.&lt;/strong&gt;  Following is an example switching function of X86_64 architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;FIBER_ASM_AMD64&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* --- save prev (rsi) --- */&lt;/span&gt;
    &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="cm"&gt;/* mov [rsi],    rsp  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x8b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="cm"&gt;/* mov rsp,      [rdi] */&lt;/span&gt;
    &lt;span class="mh"&gt;0x58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                         &lt;span class="cm"&gt;/* pop rax            */&lt;/span&gt;  &lt;span class="cm"&gt;/* entrypoint → rax */&lt;/span&gt;
    &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x6e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov [rsi+ 8], rbp  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x5e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov [rsi+16], rbx  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x4c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x66&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov [rsi+24], r12  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x4c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x6e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov [rsi+32], r13  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x4c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov [rsi+40], r14  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x4c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x7e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov [rsi+48], r15  */&lt;/span&gt;
    &lt;span class="cm"&gt;/* --- restore new (rdi) --- */&lt;/span&gt;
    &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x8b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x6f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov rbp, [rdi+ 8]  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x8b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x5f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov rbx, [rdi+16]  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x4c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x8b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov r12, [rdi+24]  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x4c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x8b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x6f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov r13, [rdi+32]  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x4c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x8b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov r14, [rdi+40]  */&lt;/span&gt;
    &lt;span class="mh"&gt;0x4c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x8b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x7f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="cm"&gt;/* mov r15, [rdi+48]  */&lt;/span&gt;
    &lt;span class="mh"&gt;0xff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xe0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="cm"&gt;/* jmp rax            */&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For some security reason, you have to align that assembly memory area properly, and protect that area.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;    &lt;span class="n"&gt;mprotect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROT_READ&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PROT_EXEC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fiber coroutine using &lt;code&gt;ucontext&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ucontext&lt;/code&gt; was a POSIX standard of context switching, very easy to handle. (&lt;code&gt;getcontext()&lt;/code&gt;, &lt;code&gt;setcontext()&lt;/code&gt;, &lt;code&gt;makecontext()&lt;/code&gt;, &lt;code&gt;swapcontext()&lt;/code&gt;) For some reason POSIX removed &lt;code&gt;ucontext&lt;/code&gt; from standard but it is still available there, because it is widely used. &lt;code&gt;glibc&lt;/code&gt; and other libc supports it. Other than X86_64 and AARCH64, I used this for posix systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fiber coroutine using &lt;code&gt;sigsetjmp/siglongjmp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is also a viable way of implementing Fiber coroutine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows Fiber
&lt;/h3&gt;

&lt;p&gt;Windows has a native &lt;code&gt;Fiber&lt;/code&gt; library.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to determine stack size?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;Fiber&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;fiber_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coroutin_fn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have to pre-determine the size of the stack. It must be at least 64 KB, and Windows Fiber defaults to be 1MB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Canary and &lt;code&gt;stack_used()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;In the debugging stage, we can prepend and postpend CANARY string to the heap allocated stack, and if stack overflows or underflows, the CANARY string gets broken and we can check it from time to time.&lt;/p&gt;

&lt;p&gt;For the stack buffer, we can fill 'Z' for the whole buffer and see how much of them are overwritten.&lt;/p&gt;

&lt;p&gt;According to that, we can see &lt;code&gt;stack_used(fiber)&lt;/code&gt; and determine appropriate stack size.&lt;/p&gt;

&lt;h3&gt;
  
  
  API functions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kr"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;Coroutine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;void&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;Fiber&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usz&lt;/span&gt; &lt;span class="n"&gt;stack_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Coroutine&lt;/span&gt; &lt;span class="n"&gt;coro&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// stack_size must be larger than 64KB, this cannot be called in other coroutine&lt;/span&gt;
&lt;span class="n"&gt;Fiber&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// get current fiber&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;switch_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// context switch&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// within a coroutine, suspend and goto main coroutine&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// you need to call this at the end of your coroutine function&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;set_debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// default is true&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;set_allocator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Allocator&lt;/span&gt; &lt;span class="n"&gt;allocx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// default is mem&lt;/span&gt;

&lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;usz&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;stack_used&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// in bytes, you can call this to determine proper stack size, not available on Windows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more information, visit &lt;a href="https://github.com/nomota/ext.c3l/blob/main/ext%2Ffiber%2FREADME.md" rel="noopener noreferrer"&gt;Fiber in C3&lt;/a&gt;&lt;/p&gt;

</description>
      <category>c</category>
      <category>computerscience</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
