lco.c 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /* lco: a coroutine library for C that minimalises stack usage
  2. Copyright (C) 2018 Ariadne Devos
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. #include <signal.h>
  15. #include <stddef.h>
  16. #include <stdio.h>
  17. #include <stdint.h>
  18. #include <stdlib.h>
  19. #include <string.h>
  20. #include "lco.h"
  21. #define LCO_DO_ASSERTIONS 1
  22. #define LCO_CLEAN_SENSITIVE 1
  23. static _Noreturn void
  24. abort_with_text(const char *msg)
  25. {
  26. (void) fputs(msg, stderr);
  27. abort();
  28. }
  29. /* XXX check for active coroutines */
  30. _Bool
  31. lco_alloc_stack(struct lco_coroutine *co)
  32. {
  33. size_t stack_size = co->stack_size;
  34. if (LCO_DO_ASSERTIONS && !stack_size)
  35. abort_with_text("lco(alloc): stack size uninitalised");
  36. if (LCO_DO_ASSERTIONS && !co->stack_array)
  37. abort_with_text("lco(alloc): stack present");
  38. if (LCO_DO_ASSERTIONS && co->pos._sp)
  39. abort_with_text("lco(alloc): coroutine started");
  40. void *stack;
  41. if (posix_memalign(&stack, LCO_STACK_ALIGNMENT, stack_size))
  42. return 1;
  43. co->stack_array = stack;
  44. return 0;
  45. }
  46. void
  47. lco_free_stack(struct lco_coroutine *co)
  48. {
  49. void *stack = co->stack_array;
  50. if (LCO_DO_ASSERTIONS && co->pos._sp)
  51. abort_with_text("lco(free): coroutine started");
  52. if (LCO_DO_ASSERTIONS && !stack)
  53. abort_with_text("lco(free): stack null");
  54. if (LCO_CLEAN_SENSITIVE) {
  55. /* TODO: poison */
  56. /* TODO: some systems do their own poisoning */
  57. /* TODO: support memset_explicit and memset_s. This function is
  58. glibc specific according to man:bzero(3). */
  59. // XXX explicit_bzero(stack, co->stack_size);
  60. }
  61. co->stack_array = NULL;
  62. free(stack);
  63. }
  64. void
  65. lco_auto_sigaltstack(size_t size, char *array)
  66. {
  67. stack_t newstack = { .ss_sp = array, .ss_flags = 0, .ss_size = size };
  68. if (sigaltstack(&newstack, NULL) == -1)
  69. abort_with_text("lco(sigaltstack)");
  70. }
  71. /* For switching away, a function is called (written in assembly).
  72. It must save registers, but it doesn't have to save all of them.
  73. (Source: System V Application Binary Interface, AMD64 Architecture Processor
  74. Supplement, Draft Version 0.99.6, Edited by Michael Matz, Jan Hubička, Andreas
  75. Jaeger, Mart Mitchell, July 2, 2012)
  76. On x86-64, the following registers are 'preserved across function calls':
  77. %rbx, %rsp, %rbp, %r12-r15, mxcsr (partially), x87 CW (Figure 3.4: Register
  78. Usage).
  79. On x86-64, sp is the top of the stack.
  80. */
  81. extern void
  82. _lco_switch(void *next_sp, void **current_sp);
  83. extern void
  84. _lco_call();
  85. typedef uint64_t interpret_u64 __attribute__((may_alias)) __attribute__((aligned(8)));
  86. typedef uint32_t interpret_u32 __attribute__((may_alias)) __attribute__((aligned(8)));
  87. typedef uint16_t interpret_u16 __attribute__((may_alias)) __attribute__((aligned(8)));
  88. void
  89. lco_init(struct lco_coroutine *co)
  90. {
  91. char *stack_array = co->stack_array;
  92. size_t stack_size = co->stack_size;
  93. if (!stack_array)
  94. abort_with_text("lco(init): stack null");
  95. if (stack_size % LCO_STACK_ALIGNMENT)
  96. abort_with_text("lco(init): stack not aligned");
  97. #if defined(__x86_64)
  98. if (stack_size < 64 + 2*8)
  99. abort_with_text("lco(init): stack too small");
  100. char *sp = stack_array + co->stack_size;
  101. /* 'Caller' */
  102. *(interpret_u64 *) (sp - 8) = 0;
  103. *(interpret_u64 *) (sp - 16) = &_lco_call;
  104. sp -= 16 + 64;
  105. /* movq (%rsp), %rbx */
  106. /* %rbx is the lco_coroutine argument to _lco_call */
  107. *(interpret_u64 *) (sp + 0) = co;
  108. /* movq 8(%rsp), %rbp */
  109. *(interpret_u64 *) (sp + 8) = 0;
  110. /* movq 16(%rsp), %r12 */
  111. *(interpret_u64 *) (sp + 16) = 0;
  112. /* movq 24(%rsp), %r13 */
  113. *(interpret_u64 *) (sp + 24) = 0;
  114. /* movq 32(%rsp), %r14 */
  115. *(interpret_u64 *) (sp + 32) = 0;
  116. /* movq 40(%rsp), %r15 */
  117. *(interpret_u64 *) (sp + 40) = 0;
  118. /* fldcw 48(%rsp) */
  119. /* Set x87 control word*/
  120. /* RC 0, PC 11, PM 1, UM 1, OM 1, ZM 1, DM 1, IM 1 is standard according
  121. to (Source: ...). */
  122. *(interpret_u64 *)(sp + 48) = 0;
  123. *(interpret_u16 *)(sp + 48) = 0b0000001100111111;
  124. /* ldmxcsr 56(%rsp) */
  125. /* Set status bits of mxcsr */
  126. /* FZ 0, RC 0, PM 1, UM 1, OM 1, ZM 1, DM 1, IM 1, DAZ 0*/
  127. *(interpret_u32 *)(sp + 52) = 0;
  128. *(interpret_u32 *)(sp + 56) = 0b0001111110000000;
  129. co->pos._sp = sp;
  130. /* Done! */
  131. #else
  132. # error Do not know the stack layout for your architecture!
  133. #endif
  134. }
  135. void
  136. lco_continue(struct lco_coroutine *co, struct lco_coroutine *current, unsigned long flags)
  137. {
  138. if (LCO_DO_ASSERTIONS && flags)
  139. abort_with_text("lco(continue): unsupported flags");
  140. if (LCO_DO_ASSERTIONS && !co->pos._sp)
  141. abort_with_text("lco(continue): inactive");
  142. if (LCO_DO_ASSERTIONS && !current->pos._sp)
  143. abort_with_text("lco(continue): inactive");
  144. if (LCO_DO_ASSERTIONS && (co == current))
  145. abort_with_text("lco(continue): equal continuations");
  146. _lco_switch(co->pos._sp, &current->pos._sp);
  147. }
  148. void
  149. lco_resume(struct lco_coroutine *co, struct lco_position *thread, unsigned long flags)
  150. {
  151. if (LCO_DO_ASSERTIONS && flags)
  152. abort_with_text("lco(resume): unsupported flags");
  153. if (LCO_DO_ASSERTIONS && !co->pos._sp)
  154. abort_with_text("lco(resume): inactive");
  155. _lco_switch(co->pos._sp, &thread->_sp);
  156. }
  157. void
  158. lco_pause(struct lco_coroutine *current, struct lco_position *thread, unsigned long flags)
  159. {
  160. if (LCO_DO_ASSERTIONS && flags)
  161. abort_with_text("lco(pause): unsupported flags");
  162. if (LCO_DO_ASSERTIONS && !current->pos._sp)
  163. abort_with_text("lco(pause): inactive");
  164. _lco_switch(thread->_sp, &current->pos._sp);
  165. }