├── .gitignore ├── LICENSE ├── README.md ├── build.zig └── src ├── arch ├── arch.zig └── x86_64 │ ├── lib.zig │ ├── linux │ ├── context.zig │ └── core.S │ └── windows │ ├── context.zig │ └── core.S ├── core.zig └── main.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-* 2 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zcoroutine 2 | `zcoroutine` is a simple coroutine library written in `Zig`. 3 | # Api 4 | ## `coInit` 5 | `coInit` / `coDeinit` is used to init / deinit the global coroutine manager. 6 | ```zig 7 | pub fn coInit(allocator: Allocator) !void; 8 | pub fn coDeinit() void; 9 | ``` 10 | example: 11 | ```zig 12 | try coInit(arena.allocator()); 13 | defer coDeinit(); 14 | ``` 15 | ## `coStart`, `yield` and `Await` 16 | `coStart` is used to start a coroutine and return a coroutine handle. 17 | 18 | `yield` is used to switch to another coroutine if have one. 19 | 20 | `Await` is a member method in CoHandle. It's used to wait for corresponding coroutine to finish running and get the return value. 21 | ```zig 22 | /// default coroutine stack size 23 | const DEFAULT_STACK_SIZE: usize = 128 * 1024; 24 | pub const CoConfig = struct { 25 | /// a name of coroutine, providing debug info 26 | /// can be aquaire by `coThisName()` inside a coroutine 27 | name: []const u8 = "unnamed", 28 | stack_size: usize = DEFAULT_STACK_SIZE, 29 | }; 30 | 31 | pub fn CoHandle(comptime Res: type) type { 32 | return struct { 33 | const Self = @This(); 34 | pub fn Await(self: *const Self) !Res; 35 | } 36 | } 37 | 38 | pub fn coStart(comptime function: anytype, args: anytype, config: ?CoConfig) !*const CoHandle(ResTypeOfFn(function)); 39 | ``` 40 | example: 41 | ```zig 42 | fn sayHi(name: []const u8, num: usize) usize { 43 | var sum: usize = 0; 44 | const cnt = (100 - num) * 100; 45 | 46 | for (0..cnt) |i| { 47 | if (i % 1000 == 0) { 48 | std.debug.print("{d} yield {d}...\n", .{num, i}); 49 | yield(); 50 | } 51 | sum += i; 52 | } 53 | 54 | std.debug.print("Hi {s}:{d} your res is = {d}\n", .{name, num, sum}); 55 | return sum; 56 | } 57 | const handle: *const CoHandle(usize) = try coStart(sayHi, .{ "Foo", i }, .{ .name = "sayHi" }); 58 | const sum = try handle.Await(); 59 | ``` -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const LazyPath = std.Build.LazyPath; 3 | const Arch = std.Target.Cpu.Arch; 4 | const OsTag = std.Target.Os.Tag; 5 | 6 | const native_os = @import("builtin").target.os.tag; 7 | const native_arch = @import("builtin").target.cpu.arch; 8 | 9 | var asm_file_str_buffer = std.mem.zeroes([100]u8); 10 | 11 | fn target_asm(arch: Arch, os: OsTag) []const u8 { 12 | return std.fmt.bufPrint(&asm_file_str_buffer, "src/arch/{s}/{s}/core.S", .{ 13 | @tagName(arch), @tagName(os) 14 | }) catch |err| std.debug.panic("[Error] Failed to generate asm file path!\nerr: {s}", .{ 15 | @errorName(err) 16 | }); 17 | } 18 | 19 | // Although this function looks imperative, note that its job is to 20 | // declaratively construct a build graph that will be executed by an external 21 | // runner. 22 | pub fn build(b: *std.Build) void { 23 | // Standard target options allows the person running `zig build` to choose 24 | // what target to build for. Here we do not override the defaults, which 25 | // means any target is allowed, and the default is native. Other options 26 | // for restricting supported target set are available. 27 | const target = b.standardTargetOptions(.{}); 28 | 29 | const target_asm_path = target_asm(target.cpu_arch orelse native_arch, target.os_tag orelse native_os); 30 | 31 | // Standard optimization options allow the person running `zig build` to select 32 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not 33 | // set a preferred release mode, allowing the user to decide how to optimize. 34 | const optimize = b.standardOptimizeOption(.{}); 35 | 36 | const lib = b.addStaticLibrary(.{ 37 | .name = "zcoroutine", 38 | // In this case the main source file is merely a path, however, in more 39 | // complicated build scripts, this could be a generated file. 40 | .root_source_file = .{ .path = "src/main.zig" }, 41 | .target = target, 42 | .optimize = optimize, 43 | }); 44 | lib.addAssemblyFile(LazyPath.relative(target_asm_path)); 45 | b.installArtifact(lib); 46 | 47 | const exe = b.addExecutable(.{ 48 | .name = "zcoroutine", 49 | // In this case the main source file is merely a path, however, in more 50 | // complicated build scripts, this could be a generated file. 51 | .root_source_file = .{ .path = "src/main.zig" }, 52 | .target = target, 53 | .optimize = optimize, 54 | }); 55 | exe.addAssemblyFile(LazyPath.relative(target_asm_path)); 56 | exe.strip = false; 57 | // This declares intent for the executable to be installed into the 58 | // standard location when the user invokes the "install" step (the default 59 | // step when running `zig build`). 60 | b.installArtifact(exe); 61 | 62 | // This *creates* a RunStep in the build graph, to be executed when another 63 | // step is evaluated that depends on it. The next line below will establish 64 | // such a dependency. 65 | const run_cmd = b.addRunArtifact(exe); 66 | 67 | // By making the run step depend on the install step, it will be run from the 68 | // installation directory rather than directly from within the cache directory. 69 | // This is not necessary, however, if the application depends on other installed 70 | // files, this ensures they will be present and in the expected location. 71 | run_cmd.step.dependOn(b.getInstallStep()); 72 | 73 | // This allows the user to pass arguments to the application in the build 74 | // command itself, like this: `zig build run -- arg1 arg2 etc` 75 | if (b.args) |args| { 76 | run_cmd.addArgs(args); 77 | } 78 | 79 | // This creates a build step. It will be visible in the `zig build --help` menu, 80 | // and can be selected like this: `zig build run` 81 | // This will evaluate the `run` step rather than the default, which is "install". 82 | const run_step = b.step("run", "Run the app"); 83 | run_step.dependOn(&run_cmd.step); 84 | 85 | // Creates a step for unit testing. 86 | const exe_tests = b.addTest(.{ 87 | .root_source_file = .{ .path = "src/main.zig" }, 88 | .target = target, 89 | .optimize = optimize, 90 | }); 91 | 92 | // Similar to creating the run step earlier, this exposes a `test` step to 93 | // the `zig build --help` menu, providing a way for the user to request 94 | // running the unit tests. 95 | const test_step = b.step("test", "Run unit tests"); 96 | test_step.dependOn(&exe_tests.step); 97 | } 98 | -------------------------------------------------------------------------------- /src/arch/arch.zig: -------------------------------------------------------------------------------- 1 | const x86_64 = @import("x86_64/lib.zig"); 2 | const native_arch = @import("builtin").target.cpu.arch; 3 | 4 | const Arch = @import("std").Target.Cpu.Arch; 5 | 6 | pub const Context: type = switch (native_arch) { 7 | Arch.x86_64 => x86_64.Context, 8 | else => @compileError("unsupport arch " ++ @tagName(native_arch)), 9 | }; 10 | 11 | pub extern fn switchCtx(cur :*Context, next: *Context) callconv(.C) void; 12 | pub extern fn switchToNext(next: *Context) callconv(.C) noreturn; 13 | pub extern fn initCall( 14 | wapper_ptr: usize, 15 | type_safe_fn_addr: usize, 16 | curr_ctx_ptr: usize, 17 | next_ctx_ptr: usize 18 | ) callconv(.C) void; 19 | -------------------------------------------------------------------------------- /src/arch/x86_64/lib.zig: -------------------------------------------------------------------------------- 1 | const OsTag = @import("std").Target.Os.Tag; 2 | const linux = @import("linux/context.zig"); 3 | const windows = @import("windows/context.zig"); 4 | 5 | const os = @import("builtin").target.os.tag; 6 | 7 | pub const Context: type = switch (os) { 8 | OsTag.linux => linux.Context, 9 | OsTag.windows => windows.Context, 10 | else => @compileError("unsupport os " ++ @tagName(os)), 11 | }; -------------------------------------------------------------------------------- /src/arch/x86_64/linux/context.zig: -------------------------------------------------------------------------------- 1 | // https://gitlab.com/x86-psABIs/x86-64-ABI/-/jobs/artifacts/master/raw/x86-64-ABI/abi.pdf?job=build 2 | // Figure 3.4: Register Usage 3 | pub const Context = extern struct { 4 | rbx: usize = 0, // 0 5 | r12: usize = 0, // 8 6 | r13: usize = 0, // 16 7 | r14: usize = 0, // 24 8 | r15: usize = 0, // 32 9 | rsp: usize = 0, // 40 10 | rbp: usize = 0, // 48 11 | rip: usize = 0, // 56 12 | 13 | const Self = @This(); 14 | 15 | pub inline fn setStack(self: *Self, sp: usize) void { 16 | self.rsp = sp; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/arch/x86_64/linux/core.S: -------------------------------------------------------------------------------- 1 | .section .text 2 | .global switchCtx 3 | .global switchToNext 4 | .global initCall 5 | .align 2 6 | # rdi: cur_ctx_ptr 7 | # rsi: next_ctx_ptr 8 | switchCtx: 9 | # save current context 10 | movq %rbx, 0(%rdi) # save rbx 11 | movq %r12, 8(%rdi) # save r12 12 | movq %r13, 16(%rdi) # save r13 13 | movq %r14, 24(%rdi) # save r14 14 | movq %r15, 32(%rdi) # save r15 15 | movq %rsp, 40(%rdi) # save rsp 16 | movq %rbp, 48(%rdi) # save rbp 17 | # for saving rip to addr of `.return` 18 | leaq .switch_ctx_to_return(%rip), %rax 19 | movq %rax, 56(%rdi) 20 | 21 | # load next context 22 | movq 0(%rsi), %rbx # load rbx 23 | movq 8(%rsi), %r12 # load r12 24 | movq 16(%rsi), %r13 # load r13 25 | movq 24(%rsi), %r14 # load r14 26 | movq 32(%rsi), %r15 # load r15 27 | movq 40(%rsi), %rsp # load rsp 28 | movq 48(%rsi), %rbp # load rbp 29 | pushq 56(%rsi) # load rip 30 | .switch_ctx_to_return: 31 | ret 32 | 33 | # rdi: next_ctx_ptr 34 | switchToNext: 35 | # load next context 36 | movq 0(%rdi), %rbx # load rbx 37 | movq 8(%rdi), %r12 # load r12 38 | movq 16(%rdi), %r13 # load r13 39 | movq 24(%rdi), %r14 # load r14 40 | movq 32(%rdi), %r15 # load r15 41 | movq 40(%rdi), %rsp # load rsp 42 | movq 48(%rdi), %rbp # load rbp 43 | pushq 56(%rdi) # load rip 44 | ret 45 | 46 | # rdi: args_ptr 47 | # rsi: typeSafeCall function address 48 | # rdx: curr_ctx_ptr 49 | # rcx: next_ctx_ptr 50 | initCall: 51 | # save curr ctx 52 | movq %rbx, 0(%rdx) # save rbx 53 | movq %r12, 8(%rdx) # save r12 54 | movq %r13, 16(%rdx) # save r13 55 | movq %r14, 24(%rdx) # save r14 56 | movq %r15, 32(%rdx) # save r15 57 | movq %rsp, 40(%rdx) # save rsp 58 | movq %rbp, 48(%rdx) # save rbp 59 | # for saving rip to addr of `.return` 60 | leaq .init_call_to_return(%rip), %rbx 61 | movq %rbx, 56(%rdx) 62 | 63 | # load coroutine stack 64 | movq 40(%rcx), %rsp 65 | 66 | # call typeSafeCall function 67 | call *%rsi 68 | 69 | .init_call_to_return: 70 | ret -------------------------------------------------------------------------------- /src/arch/x86_64/windows/context.zig: -------------------------------------------------------------------------------- 1 | // https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#callercallee-saved-registers 2 | pub const Context = extern struct { 3 | rbx: usize = 0, // 0 4 | r12: usize = 0, // 8 5 | r13: usize = 0, // 16 6 | r14: usize = 0, // 24 7 | r15: usize = 0, // 32 8 | rdi: usize = 0, // 40 9 | rsi: usize = 0, // 48 10 | rsp: usize = 0, // 56 11 | rbp: usize = 0, // 64 12 | rip: usize = 0, // 72 13 | 14 | xmm6: f128 = 0, // 80 15 | xmm7: f128 = 0, // 96 16 | xmm8: f128 = 0, // 112 17 | xmm9: f128 = 0, // 128 18 | xmm10: f128 = 0, // 144 19 | xmm11: f128 = 0, // 160 20 | xmm12: f128 = 0, // 176 21 | xmm13: f128 = 0, // 192 22 | xmm14: f128 = 0, // 208 23 | xmm15: f128 = 0, // 224 24 | 25 | const Self = @This(); 26 | 27 | pub inline fn setStack(self: *Self, sp: usize) void { 28 | self.rsp = sp; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/arch/x86_64/windows/core.S: -------------------------------------------------------------------------------- 1 | .section .text 2 | .global switchCtx 3 | .global switchToNext 4 | .global initCall 5 | .align 2 6 | # rcx: cur_ctx_ptr 7 | # rdx: next_ctx_ptr 8 | switchCtx: 9 | # save cur context 10 | movq %rbx, 0(%rcx) # save rbx 11 | movq %r12, 8(%rcx) # save r12 12 | movq %r13, 16(%rcx) # save r13 13 | movq %r14, 24(%rcx) # save r14 14 | movq %r15, 32(%rcx) # save r15 15 | movq %rdi, 40(%rcx) # save rdi 16 | movq %rsi, 48(%rcx) # save rsi 17 | movq %rsp, 56(%rcx) # save rsp 18 | movq %rbp, 64(%rcx) # save rbp 19 | movups %xmm6, 80(%rcx) # save xmm6 20 | movups %xmm7, 96(%rcx) # save xmm7 21 | movups %xmm8, 112(%rcx) # save xmm8 22 | movups %xmm9, 128(%rcx) # save xmm9 23 | movups %xmm10, 144(%rcx) # save xmm10 24 | movups %xmm11, 160(%rcx) # save xmm11 25 | movups %xmm12, 176(%rcx) # save xmm12 26 | movups %xmm13, 192(%rcx) # save xmm13 27 | movups %xmm14, 208(%rcx) # save xmm14 28 | movups %xmm15, 224(%rcx) # save xmm15 29 | # for saving rip to addr of `.return` 30 | leaq .switch_ctx_to_return(%rip), %rax 31 | movq %rax, 72(%rcx) 32 | 33 | # load next context 34 | movq 0(%rdx), %rbx # load rbx 35 | movq 8(%rdx), %r12 # load r12 36 | movq 16(%rdx), %r13 # load r13 37 | movq 24(%rdx), %r14 # load r14 38 | movq 32(%rdx), %r15 # load r15 39 | movq 40(%rdx), %rdi # load rdi 40 | movq 48(%rdx), %rsi # load rsi 41 | movq 56(%rdx), %rsp # load rsp 42 | movq 64(%rdx), %rbp # load rbp 43 | movups 80(%rdx), %xmm6 # load xmm6 44 | movups 96(%rdx), %xmm7 # load xmm7 45 | movups 112(%rdx), %xmm8 # load xmm8 46 | movups 128(%rdx), %xmm9 # load xmm9 47 | movups 144(%rdx), %xmm10 # load xmm10 48 | movups 160(%rdx), %xmm11 # load xmm11 49 | movups 176(%rdx), %xmm12 # load xmm12 50 | movups 192(%rdx), %xmm13 # load xmm13 51 | movups 208(%rdx), %xmm14 # load xmm14 52 | movups 224(%rdx), %xmm15 # load xmm15 53 | pushq 72(%rdx) # load rip 54 | .switch_ctx_to_return: 55 | ret 56 | 57 | switchToNext: 58 | # load next context 59 | movq 0(%rcx), %rbx # load rbx 60 | movq 8(%rcx), %r12 # load r12 61 | movq 16(%rcx), %r13 # load r13 62 | movq 24(%rcx), %r14 # load r14 63 | movq 32(%rcx), %r15 # load r15 64 | movq 40(%rcx), %rdi # load rdi 65 | movq 48(%rcx), %rsi # load rsi 66 | movq 56(%rcx), %rsp # load rsp 67 | movq 64(%rcx), %rbp # load rbp 68 | movups 80(%rcx), %xmm6 # load xmm6 69 | movups 96(%rcx), %xmm7 # load xmm7 70 | movups 112(%rcx), %xmm8 # load xmm8 71 | movups 128(%rcx), %xmm9 # load xmm9 72 | movups 144(%rcx), %xmm10 # load xmm10 73 | movups 160(%rcx), %xmm11 # load xmm11 74 | movups 176(%rcx), %xmm12 # load xmm12 75 | movups 192(%rcx), %xmm13 # load xmm13 76 | movups 208(%rcx), %xmm14 # load xmm14 77 | movups 224(%rcx), %xmm15 # load xmm15 78 | pushq 72(%rcx) # load rip 79 | ret 80 | 81 | # rcx: args_ptr 82 | # rdx: typeSafeCall function address 83 | # r8: curr_ctx_ptr 84 | # r9: next_ctx_ptr 85 | initCall: 86 | # save curr ctx 87 | movq %rbx, 0(%r8) # save rbx 88 | movq %r12, 8(%r8) # save r12 89 | movq %r13, 16(%r8) # save r13 90 | movq %r14, 24(%r8) # save r14 91 | movq %r15, 32(%r8) # save r15 92 | movq %rdi, 40(%r8) # save rdi 93 | movq %rsi, 48(%r8) # save rsi 94 | movq %rsp, 56(%r8) # save rsp 95 | movq %rbp, 64(%r8) # save rbp 96 | movups %xmm6, 80(%r8) # save xmm6 97 | movups %xmm7, 96(%r8) # save xmm7 98 | movups %xmm8, 112(%r8) # save xmm8 99 | movups %xmm9, 128(%r8) # save xmm9 100 | movups %xmm10, 144(%r8) # save xmm10 101 | movups %xmm11, 160(%r8) # save xmm11 102 | movups %xmm12, 176(%r8) # save xmm12 103 | movups %xmm13, 192(%r8) # save xmm13 104 | movups %xmm14, 208(%r8) # save xmm14 105 | movups %xmm15, 224(%r8) # save xmm15 106 | # for saving rip to addr of `.return` 107 | leaq .init_call_to_return(%rip), %rbx 108 | movq %rbx, 72(%r8) 109 | 110 | # load coroutine stack 111 | movq 56(%r9), %rsp 112 | 113 | # call typeSafeCall function 114 | call *%rdx 115 | 116 | .init_call_to_return: 117 | ret -------------------------------------------------------------------------------- /src/core.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const arch = @import("arch/arch.zig"); 3 | const Context = arch.Context; 4 | const Allocator = @import("std").mem.Allocator; 5 | 6 | const Timestamp = i64; 7 | 8 | const MAIN_ID: usize = 0; 9 | var MANAGER: ?Manager = null; 10 | 11 | // TODO create a time module 12 | // TODO introduce a internal noreturn yield 13 | 14 | /// get timestamp in microsecond 15 | inline fn now() Timestamp { 16 | return std.time.microTimestamp(); 17 | } 18 | 19 | /// a helper function to detect function return type 20 | fn ResTypeOfFn(comptime function: anytype) type { 21 | return @typeInfo(@TypeOf(function)).Fn.return_type orelse void; 22 | } 23 | 24 | pub inline fn coInit(allocator: Allocator) !void { 25 | if (MANAGER == null) { 26 | MANAGER = try Manager.init(allocator); 27 | } 28 | } 29 | 30 | pub inline fn coDeinit() void { 31 | MANAGER.?.deinit(); 32 | } 33 | 34 | pub inline fn coStart(comptime function: anytype, args: anytype, config: ?CoConfig) !*const CoHandle(ResTypeOfFn(function)) { 35 | const conf = config orelse CoConfig{}; 36 | return MANAGER.?.coStart(function, args, conf); 37 | } 38 | 39 | pub inline fn coExit() noreturn { 40 | MANAGER.?.coExit(); 41 | } 42 | 43 | pub inline fn coSleep(us: u32) !void { 44 | try MANAGER.?.coSleep(us); 45 | } 46 | 47 | pub inline fn yield() void { 48 | MANAGER.?.yield() catch |err| { 49 | std.debug.panic("[ERROR] {s} in yield", .{@errorName(err)}); 50 | }; 51 | return; 52 | } 53 | 54 | inline fn currentCCBPtr() *CCB { 55 | return MANAGER.?.getCurrentCCBPtr(); 56 | } 57 | 58 | inline fn markCurFinished() void { 59 | MANAGER.?.markCurFinished(); 60 | } 61 | 62 | /// default coroutine stack size 63 | const DEFAULT_STACK_SIZE: usize = 128 * 1024; 64 | pub const CoConfig = struct { 65 | /// a name of coroutine, providing debug info 66 | /// can be aquaire by `coThisName()` inside a coroutine 67 | name: []const u8 = "unnamed", 68 | stack_size: usize = DEFAULT_STACK_SIZE, 69 | }; 70 | 71 | pub const CoError = error{ 72 | /// a coroutine is terminated by accident 73 | UnexpectedTerminate, 74 | /// await on dead coroutine 75 | AwaitOnDeadCoroutine, 76 | }; 77 | 78 | /// a handle to a coroutine 79 | pub fn CoHandle(comptime Res: type) type { 80 | return struct { 81 | /// id for this Coroutine 82 | id: usize, 83 | /// a coroutine result 84 | /// aquire using cawait 85 | res: ?Res, 86 | allocator: Allocator, 87 | 88 | const Self = @This(); 89 | 90 | pub fn Await(self: *const Self) !Res { 91 | while (true) { 92 | if (MANAGER.?.getCCBPtr(self.id)) |ccb_ptr| { 93 | switch (ccb_ptr.status) { 94 | .Active, .Sleep, .Frozen => yield(), 95 | .Finished => { 96 | defer self.deinit(); 97 | if (Res != void) { 98 | const res = self.res.?; 99 | try MANAGER.?.markCoDeadById(self.id); 100 | return res; 101 | } else { 102 | return {}; 103 | } 104 | }, 105 | .Dead => { 106 | return CoError.AwaitOnDeadCoroutine; 107 | }, 108 | } 109 | } else { 110 | return CoError.UnexpectedTerminate; 111 | } 112 | } 113 | } 114 | 115 | fn init(id: usize, allocator: Allocator) !*Self { 116 | var self = try allocator.create(Self); 117 | 118 | self.id = id; 119 | self.allocator = allocator; 120 | self.res = null; 121 | 122 | return self; 123 | } 124 | 125 | inline fn deinit(self: *const Self) void { 126 | self.allocator.destroy(self); 127 | } 128 | }; 129 | } 130 | 131 | const CCB = struct { 132 | context: Context = Context{}, 133 | 134 | id: usize, 135 | name: []const u8 = "unnamed", 136 | 137 | start_time: Timestamp, 138 | elapsed: i64 = 0, 139 | status: Status = Status.Active, 140 | 141 | handle_addr: usize = 0, 142 | 143 | /// stack space of this coroutine 144 | /// must align to 16 bit 145 | stack: []align(16) u8, 146 | 147 | pub const Status = enum(usize) { 148 | /// the coroutine is ready or running 149 | Active = 0, 150 | Sleep = 1, 151 | /// the coroutine is frozen and waiting for waken by a waker 152 | Frozen = 2, 153 | /// the coroutine is finished, but is not marked as Dead by coAwait 154 | /// and is removed from the waiting list which means it would not be wake up again 155 | Finished = 3, 156 | /// the coroutine is marked as Dead by coAwait, waiting for destory 157 | /// (free its stack space and itself) 158 | Dead = 4, 159 | }; 160 | 161 | const Self = @This(); 162 | 163 | pub fn init( 164 | self: *Self, 165 | allocator: Allocator, 166 | id: usize, 167 | config: CoConfig, 168 | ) !void { 169 | // allocate a stack space for this coroutine 170 | // need to align stack space to 16 171 | std.debug.assert(config.stack_size % 16 == 0); 172 | const stack = try allocator.alignedAlloc(u8, 16, config.stack_size); 173 | self.stack = stack; 174 | 175 | // init context 176 | self.context = Context{}; 177 | // then we need to allocate a ptr space for keeping this coroutine's handle ptr 178 | const sp = @intFromPtr(stack.ptr) + stack.len; 179 | self.context.setStack(sp); 180 | 181 | self.id = id; 182 | self.name = config.name; 183 | 184 | self.elapsed = 0; 185 | self.start_time = now(); 186 | 187 | self.status = Status.Active; 188 | } 189 | 190 | pub fn compare(_: void, lhs: *Self, rhs: *Self) std.math.Order { 191 | return std.math.order(lhs.elapsed, rhs.elapsed); 192 | } 193 | 194 | pub inline fn tick(self: *Self, t_now: Timestamp) void { 195 | self.elapsed = t_now - self.start_time; 196 | } 197 | 198 | inline fn deinit(self: *const Self, allocator: Allocator) void { 199 | allocator.free(self.stack); 200 | } 201 | }; 202 | 203 | pub fn ArgsWrapper(comptime Args: type) type { 204 | return struct { 205 | args: Args, 206 | }; 207 | } 208 | 209 | fn TypeSafeCallTable(comptime Args: type, comptime Res: type, comptime function: anytype) type { 210 | return struct { 211 | const Self = @This(); 212 | 213 | pub inline fn getAddr() usize { 214 | return @intFromPtr(&Self.call); 215 | } 216 | 217 | fn call(wrapper: *ArgsWrapper(Args)) callconv(.C) noreturn { 218 | const res = @call(.auto, function, wrapper.args); 219 | if (Res != void) { 220 | // handle is allocacted on heap, so this pointer is still vaild 221 | var h: *CoHandle(Res) = @ptrFromInt(currentCCBPtr().handle_addr); 222 | // now the coroutine return we sure that we now in this coroutine's ctx 223 | // that means: 224 | // this is *current* coroutine 225 | // if the res is a value type, it can safely copy from stack 226 | // if the res is a pointer type, it should point to space on heap 227 | // or on its stack, which is also valid 228 | h.res = res; 229 | } 230 | coExit(); 231 | } 232 | 233 | fn forzenCall(wrapper: *ArgsWrapper(Args)) callconv(.C) noreturn { 234 | const args = wrapper.*; 235 | asm volatile ("" ::: "memory"); 236 | // forzen at this point 237 | yield(); 238 | const res = @call(.auto, function, args); 239 | if (Res != void) { 240 | var h: *CoHandle(Res) = @ptrFromInt(currentCCBPtr().handle_addr); 241 | h.res = res; 242 | } 243 | coExit(); 244 | } 245 | }; 246 | } 247 | 248 | const Manager = struct { 249 | next_unused_id: usize = 1, 250 | 251 | ccb_container: CCBContainer, 252 | allocator: Allocator, 253 | 254 | const Self = @This(); 255 | 256 | pub fn init(allocator: Allocator) !Self { 257 | const ccb_container = try CCBContainer.init(allocator); 258 | return Self{ 259 | .ccb_container = ccb_container, 260 | .allocator = allocator, 261 | }; 262 | } 263 | 264 | pub inline fn deinit(self: *Self) void { 265 | self.ccb_container.deinit(); 266 | } 267 | 268 | pub inline fn getMainCCBPtr(self: *Self) *CCB { 269 | return self.ccb_container.getMainCCBPtr(); 270 | } 271 | 272 | pub inline fn getCurrentCCBPtr(self: *Self) *CCB { 273 | return self.ccb_container.cur_ccb; 274 | } 275 | 276 | pub inline fn markCurFinished(self: *Self) void { 277 | self.ccb_container.cur_ccb.status = .Finished; 278 | } 279 | 280 | pub inline fn markCoDeadById(self: *Self, target_id: usize) !void { 281 | try self.ccb_container.markDead(target_id); 282 | } 283 | 284 | pub inline fn getCCBPtr(self: *Self, target_id: usize) ?*CCB { 285 | return self.ccb_container.binarySearchCCB(target_id); 286 | } 287 | 288 | pub fn coStart( 289 | self: *Self, 290 | comptime function: anytype, 291 | args: anytype, 292 | config: CoConfig) !*const CoHandle(ResTypeOfFn(function)) 293 | { 294 | const Args = @TypeOf(args); 295 | const Res = ResTypeOfFn(function); 296 | // emplace a new ccb in cbb_container and inc next_id 297 | const new_ccb_ptr: *CCB = try self.ccb_container.emplace(self.next_unused_id, config); 298 | self.next_unused_id += 1; 299 | 300 | // alloc a handle for new coroutine 301 | const handle_ptr = try CoHandle(Res).init(new_ccb_ptr.id, self.allocator); 302 | // regist handle on ccb 303 | new_ccb_ptr.handle_addr = @intFromPtr(handle_ptr); 304 | // wrap args in a struct to get their address 305 | const args_wapper = ArgsWrapper(Args){ 306 | .args = args, 307 | }; 308 | 309 | // update cur 310 | const cur_ccb = self.getCurrentCCBPtr(); 311 | const t_now = now(); 312 | cur_ccb.tick(t_now); 313 | try self.ccb_container.addToWaitlist(cur_ccb); 314 | // move to next 315 | self.ccb_container.cur_ccb = new_ccb_ptr; 316 | 317 | const type_safe_fn_addr = TypeSafeCallTable(Args, Res, function).getAddr(); 318 | arch.initCall( 319 | @intFromPtr(&args_wapper), 320 | type_safe_fn_addr, 321 | @intFromPtr(&cur_ccb.context), 322 | @intFromPtr(&new_ccb_ptr.context) 323 | ); 324 | 325 | return handle_ptr; 326 | } 327 | 328 | pub fn coExit(self: *Self) noreturn { 329 | const t_now = now(); 330 | self.ccb_container.cur_ccb.status = .Finished; 331 | const next = self.ccb_container.moveToNext(t_now).?; 332 | arch.switchToNext(&next.context); 333 | } 334 | 335 | pub fn coSleep(self: *Self, us: u32) !void { 336 | const t_now = now(); 337 | const cur_ccb = self.getCurrentCCBPtr(); 338 | cur_ccb.tick(t_now); 339 | cur_ccb.status = .Sleep; 340 | const wake_at = t_now + @as(Timestamp, @intCast(us)); 341 | try self.ccb_container.addToSleeplist(cur_ccb.id, wake_at); 342 | if (self.ccb_container.moveToNext(t_now)) |next_ccb| { 343 | if (next_ccb.id == cur_ccb.id) { 344 | return; 345 | } 346 | arch.switchCtx(&cur_ccb.context, &next_ccb.context); 347 | } else { 348 | std.time.sleep(@intCast(us * std.time.ns_per_us)); 349 | } 350 | } 351 | 352 | pub fn yield(self: *Self) !void { 353 | const t_now = now(); 354 | const cur_ccb = self.getCurrentCCBPtr(); 355 | if (self.ccb_container.moveToNext(t_now)) |next_ccb| { 356 | if (cur_ccb.status == .Active) { 357 | cur_ccb.tick(t_now); 358 | try self.ccb_container.addToWaitlist(cur_ccb); 359 | } 360 | arch.switchCtx(&cur_ccb.context, &next_ccb.context); 361 | } 362 | return; 363 | } 364 | }; 365 | 366 | const CCBContainer = struct { 367 | /// a pointer to current ccb 368 | cur_ccb: *CCB, 369 | /// a dyn array that store the actual coroutine control block data 370 | /// ccb.id will consist in accending order 371 | ccb_data: []CCB, 372 | /// total num of cbbs 373 | size: usize = 1, 374 | /// the num of alive(Active and Finished) ccb 375 | alive: usize = 1, 376 | /// a priority dequeue that manage ccb by their elapsed time 377 | /// a coroutine waitlist 378 | waitlist: WaitList, 379 | /// a priority dequeue that manage sleep ccb by their wake_at time 380 | sleeplist: SleepList, 381 | 382 | allocator: Allocator, 383 | 384 | const INIT_CAP: usize = 8; 385 | 386 | const SleepCCB = struct { 387 | id: usize, 388 | wake_at: Timestamp, 389 | pub fn compare(_:void, a: @This(), b: @This()) std.math.Order { 390 | return std.math.order(a.wake_at, b.wake_at); 391 | } 392 | }; 393 | const Self = @This(); 394 | const SleepList = std.PriorityDequeue(SleepCCB, void, SleepCCB.compare); 395 | const WaitList = std.PriorityDequeue(*CCB, void, CCB.compare); 396 | 397 | 398 | pub fn init(allocator: Allocator) !Self { 399 | const ccb_data: []CCB = try allocator.alloc(CCB, INIT_CAP); 400 | // init main ccb 401 | const main_ccb: *CCB = &ccb_data[0]; 402 | try main_ccb.init(allocator, MAIN_ID, CoConfig{ 403 | .name = "main", 404 | // allocate a dummy stack for main 405 | .stack_size = 0, 406 | }); 407 | const waitlist = WaitList.init(allocator, {}); 408 | const sleeplist = SleepList.init(allocator, {}); 409 | return Self { 410 | .cur_ccb = main_ccb, 411 | .ccb_data = ccb_data, 412 | .waitlist = waitlist, 413 | .sleeplist = sleeplist, 414 | .allocator = allocator, 415 | }; 416 | } 417 | 418 | pub fn deinit(self: *Self) void { 419 | self.waitlist.deinit(); 420 | const len = self.size; 421 | for (self.ccb_data[0..len]) |ccb| { 422 | ccb.deinit(self.allocator); 423 | } 424 | self.allocator.free(self.ccb_data); 425 | } 426 | 427 | pub inline fn getMainCCBPtr(self: *Self) *CCB { 428 | std.debug.assert(self.ccb_data[0].id == MAIN_ID); 429 | return &self.ccb_data[0]; 430 | } 431 | 432 | pub inline fn addToWaitlist(self: *Self, ccb_ptr: *CCB) !void { 433 | try self.waitlist.add(ccb_ptr); 434 | } 435 | 436 | pub inline fn addToSleeplist(self: *Self, id: usize, wake_at: Timestamp) !void { 437 | const sleep_ccb = SleepCCB { 438 | .id = id, 439 | .wake_at = wake_at, 440 | }; 441 | try self.sleeplist.add(sleep_ccb); 442 | } 443 | 444 | /// this function will return next coroutine if have one 445 | /// 1. look up sleep coroutine that has the smallest wake_at 446 | /// which means it should be waken first. 447 | /// if it should be waken up then we set cur_ccb to it and 448 | /// set its status to Active 449 | /// if not we record this ccb 450 | /// 2. we search in the waitlist if have one we set cur_ccb to it 451 | /// and return it 452 | /// 3. if previous steps failed, then we check if we had recorded 453 | /// a sleep ccb. if so, we remove it from the sleep list, 454 | /// then just wait for it to wake up, set next ccb to it, 455 | /// set its status to Active and return it 456 | pub fn moveToNext(self: *Self, t_now: Timestamp) ?*CCB { 457 | // first we try to wake up the sleep coroutine 458 | const DELTA: Timestamp = -1000; 459 | var op_sleep_ccb: ?*CCB = null; 460 | var wake_at: Timestamp = 0; 461 | if (self.sleeplist.peekMin()) |sleep_ccb| { 462 | if (self.binarySearchCCB(sleep_ccb.id)) |next_ccb_ptr| { 463 | const diff = t_now - sleep_ccb.wake_at; 464 | if (diff >= DELTA) { 465 | // remove this coroutine from sleeplist 466 | _ = self.sleeplist.removeMin(); 467 | // set the coroutine status to Active 468 | self.cur_ccb = next_ccb_ptr; 469 | self.cur_ccb.status = .Active; 470 | return next_ccb_ptr; 471 | } else { 472 | // recored the sleep ccb we find 473 | op_sleep_ccb = next_ccb_ptr; 474 | wake_at = sleep_ccb.wake_at; 475 | } 476 | } 477 | } 478 | // then we search on waitlist 479 | if (self.waitlist.removeMinOrNull()) |next_ccb_ptr| { 480 | self.cur_ccb = next_ccb_ptr; 481 | return next_ccb_ptr; 482 | } 483 | // if no ccb on wait list and we found a sleep ccb 484 | // then we wait for the sleep ccb 485 | if (op_sleep_ccb) |sleep_ccb| { 486 | // remove from the sleep list 487 | _ = self.sleeplist.removeMin(); 488 | const us_sleep = wake_at - now(); 489 | const nano_sleep = @max(0, us_sleep) * std.time.ns_per_us; 490 | std.time.sleep(@intCast(nano_sleep)); 491 | self.cur_ccb = sleep_ccb; 492 | self.cur_ccb.status = .Active; 493 | return sleep_ccb; 494 | } 495 | 496 | return null; 497 | } 498 | 499 | /// emplace a CCB in ccb_data 500 | /// return a ptr to new CCB if success 501 | pub fn emplace(self: *Self, id: usize, config: CoConfig) !*CCB { 502 | try self.ensureUnusedCapacity(1); 503 | const new_ccb_ptr = &self.ccb_data[self.size]; 504 | try new_ccb_ptr.init(self.allocator, id, config); 505 | self.size += 1; 506 | self.alive += 1; 507 | 508 | return new_ccb_ptr; 509 | } 510 | 511 | /// search a CCB with target_id 512 | pub fn binarySearchCCB(self: *Self, target_id: usize) ?*CCB { 513 | var left: isize = 0; 514 | var right: isize = @as(isize, @intCast(self.size)) - 1; 515 | while (left <= right) { 516 | const mid = @divTrunc(right - left, 2) + left; 517 | const m_id = self.ccb_data[@intCast(mid)].id; 518 | if (m_id < target_id) { 519 | left = mid + 1; 520 | } else if (m_id == target_id) { 521 | return &self.ccb_data[@intCast(mid)]; 522 | } else { 523 | right = mid - 1; 524 | } 525 | } 526 | 527 | return null; 528 | } 529 | 530 | /// mark a coroutine with given id as Dead, update the alive num. 531 | /// if alive num < 1/3 * size then remove the dead corotine from ccb_data 532 | /// 533 | /// if ccb_data does not contain a ccb with given id, 534 | /// this function make no effects 535 | pub fn markDead(self: *Self, id: usize) !void { 536 | if (self.binarySearchCCB(id)) |target_ccb| { 537 | // set coroutine status 538 | target_ccb.status = .Dead; 539 | // update alive num 540 | self.alive -= 1; 541 | const limit = self.size / 3; // maybe overflow 542 | if (self.alive < limit) { 543 | try self.removeAllDead(); 544 | } 545 | } 546 | } 547 | 548 | /// remove dead coroutine from ccb_data 549 | fn removeAllDead(self: *Self) !void { 550 | const limit = self.ccb_data.len / 4; 551 | const cur_id = self.cur_ccb.id; 552 | if (self.alive < limit) { 553 | try self.removeDeadWithAlloc(); 554 | } else { 555 | self.removeDeadInPlace(); 556 | } 557 | std.debug.assert(self.alive == self.size); 558 | // remap to waitlist 559 | try self.remapToWaitlist(cur_id); 560 | } 561 | 562 | /// remove dead coroutine and move alive coroutine to new space 563 | /// 564 | /// 1. alloc new space for alive coroutine (cap = alive * 1.5) 565 | /// 2. copy alive coroutine to new space uisng two pointer alg, 566 | /// deinit dead coroutine 567 | /// 3. free old space and set ccb_data to new space 568 | /// 4. updata the size 569 | fn removeDeadWithAlloc(self: *Self) !void { 570 | const new_space_size = self.alive * 3 / 2; // maybe overflow 571 | const new_space = try self.allocator.alloc(CCB, new_space_size); 572 | // copy alive coroutine to new space uisng two pointer alg 573 | // and deinit dead coroutine 574 | var new_idx: usize = 0; 575 | for (self.ccb_data[0..self.size]) |ccb| { 576 | if (ccb.status == .Dead) { 577 | ccb.deinit(self.allocator); 578 | } else { 579 | new_space[new_idx] = ccb; 580 | new_idx += 1; 581 | } 582 | } 583 | // free old space and set ccb_data to new space 584 | self.allocator.free(self.ccb_data); 585 | self.ccb_data = new_space; 586 | // updata the size 587 | self.size = new_idx; 588 | } 589 | 590 | /// remove dead coroutine inplace using two pointer alg 591 | /// 592 | /// 1. remove dead coroutine using two poiner alg 593 | /// 2. deinit dead coroutine while doing 1 594 | /// 3. update the size 595 | fn removeDeadInPlace(self: *Self) void { 596 | var alive_idx: usize = 0; 597 | for (self.ccb_data[0..self.size]) |ccb| { 598 | if (ccb.status == .Dead) { 599 | ccb.deinit(self.allocator); 600 | } else { 601 | self.ccb_data[alive_idx] = ccb; 602 | alive_idx += 1; 603 | } 604 | } 605 | self.size = alive_idx; 606 | } 607 | 608 | fn ensureUnusedCapacity(self: *Self, unused_cap: usize) !void { 609 | if (self.ccb_data.len >= self.size + unused_cap) { 610 | return; 611 | } 612 | // record cur id for remapTowaitlist 613 | const cur_id = self.cur_ccb.id; 614 | // need to allocate new space 615 | const new_cap = self.ccb_data.len * 2; 616 | var new_ccb_space = try self.allocator.alloc(CCB, new_cap); 617 | std.mem.copy(CCB, new_ccb_space, self.ccb_data[0..self.size]); 618 | 619 | self.allocator.free(self.ccb_data); 620 | self.ccb_data = new_ccb_space; 621 | 622 | // now all ccb ptrs in waitlist is no longer valid 623 | // we need to add the new ccb ptrs to it 624 | try self.remapToWaitlist(cur_id); 625 | } 626 | 627 | /// when this function be called. we know that ccb_data is change 628 | /// that means: 629 | /// 1. cur_ptr may not be valid 630 | /// 2. ptr in waitlist may not be valid 631 | /// 632 | /// so we need to remap the ptr to waitlist and set cur_ptr and 633 | /// we don't want cur ptr be added to queue 634 | /// 635 | /// note that we cannot relay on self.cur_ccb because it may not be valid anymore 636 | fn remapToWaitlist(self: *Self, cur_id: usize) !void { 637 | self.waitlist.len = 0; 638 | for (0..self.size) |idx| { 639 | const ccb_ptr = &self.ccb_data[idx]; 640 | if (ccb_ptr.id == cur_id) { 641 | self.cur_ccb = ccb_ptr; 642 | continue; 643 | } 644 | if (ccb_ptr.status == .Active) { 645 | try self.addToWaitlist(ccb_ptr); 646 | } 647 | } 648 | } 649 | }; 650 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const core = @import("core.zig"); 4 | 5 | pub const yield = core.yield; 6 | pub const coInit = core.coInit; 7 | pub const coStart = core.coStart; 8 | pub const coSleep = core.coSleep; 9 | pub const coDeinit = core.coDeinit; 10 | pub const CoHandle = core.CoHandle; 11 | pub const CoConfig = core.CoConfig; 12 | 13 | fn sleepTest(s: u32) void { 14 | std.debug.print("[Trace] {d} Try to sleep {d} s.\n", .{s, (10 - s)}); 15 | coSleep((10 - s) * 1000 * 1000) catch return; 16 | std.debug.print("[Trace] {d} wake up!\n", .{s}); 17 | } 18 | 19 | fn sum(start: usize, end: usize) usize { 20 | var total: usize = 0; 21 | for (start..end) |v| { 22 | total += v; 23 | if (v % 1000 == 0) { 24 | std.debug.print("co {} yield\n", .{start}); 25 | core.yield(); 26 | std.debug.print("co {} yield return\n", .{start}); 27 | } 28 | } 29 | 30 | return total; 31 | } 32 | 33 | fn child(id: usize) void { 34 | std.debug.print("[Trace] child {} start !\n", .{ id }); 35 | std.debug.print("[Trace] Sleep for {}s\n", .{ id }); 36 | core.coSleep(@intCast(1000 * 1000 * id)) catch return; 37 | std.debug.print("[Trace] child {} wake up\n", .{ id }); 38 | } 39 | 40 | fn spawnChildren(allocator: std.mem.Allocator) void { 41 | var handles = std.ArrayList(*const CoHandle(void)).init(allocator); 42 | defer handles.deinit(); 43 | 44 | for (0..10) |i| { 45 | const handle = coStart(child, .{@as(u32, @intCast(i))}, null) catch return; 46 | handles.append(handle) catch return; 47 | } 48 | 49 | for (handles.items) |handle| { 50 | handle.Await() catch return; 51 | } 52 | } 53 | 54 | pub fn main() !void { 55 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 56 | // init coroutine manager 57 | try coInit(arena.allocator()); 58 | defer coDeinit(); 59 | 60 | var handles = std.ArrayList(*const CoHandle(void)).init(arena.allocator()); 61 | defer handles.deinit(); 62 | for (0..10) |i| { 63 | const handle = try coStart(sleepTest, .{@as(u32, @intCast(i))}, null); 64 | try handles.append(handle); 65 | } 66 | 67 | const spawn_handle = try coStart(spawnChildren, .{ arena.allocator() }, null); 68 | 69 | var sum_handles = std.ArrayList(*const CoHandle(usize)).init(arena.allocator()); 70 | defer sum_handles.deinit(); 71 | for (0..10) |i| { 72 | const handle = try coStart(sum, .{ i * 10000, (i + 1) * 10000 }, null); 73 | try sum_handles.append(handle); 74 | } 75 | 76 | try spawn_handle.Await(); 77 | 78 | for (sum_handles.items, 0..) |handle, i| { 79 | const total = try handle.Await(); 80 | std.debug.print("sum {} = {}\n", .{i*10000, total}); 81 | } 82 | 83 | for (handles.items) |handle| { 84 | try handle.Await(); 85 | } 86 | } 87 | --------------------------------------------------------------------------------