diff options
| author | Robin Haberkorn <rhaberkorn@fmsbw.de> | 2026-08-22 22:27:33 +0200 |
|---|---|---|
| committer | Robin Haberkorn <rhaberkorn@fmsbw.de> | 2026-08-22 22:27:33 +0200 |
| commit | 2e097cec409182c3cb39b74489387480bf8a278f (patch) | |
| tree | 47f501fa2634835cc0c70a9214f01b32f49b9d30 /src/parser.h | |
| parent | 3ae89e34f6a04dd553f3871856521334a4dbe62f (diff) | |
optimize main state machine transitions with tail calls
* On newer GCC (>= 15) and Clang (>= 13) versions we can tail call
at the end of input_cb() implementations to the next state's input_cb(),
which will be optimized to jumps (often direct jumps).
That is, the compilers always optimized tail calls, but we can
guarantee tail calls with the __attribute__((musttail)) statement
attribute.
* Every `return &teco_state_xxx` has to be replaced with
`TECO_RETURN(ctx, &teco_state_xxx, error)`.
* This speeds up `-O2 -flto` builds by 18%
(e.g. tested on grosciteco for sciteco(7)).
Part of the speed up could also be because of inlining through
TECO_RETURN().
* The other state machines (stringbuilding and q-reg spec) cannot
currently be optimized the same way since they get their characters
passed in from the "main" state machine.
* All loops around callbacks could be optimized the same way.
E.g. the undo token runner could also tail call into the next runner,
but it's probably not important to optimize undo token executions.
Diffstat (limited to 'src/parser.h')
| -rw-r--r-- | src/parser.h | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/src/parser.h b/src/parser.h index 0c389cc..75a3432 100644 --- a/src/parser.h +++ b/src/parser.h @@ -25,6 +25,7 @@ #include "goto.h" #include "undo.h" #include "qreg.h" +#include "memory.h" #include "lexer.h" /* @@ -481,8 +482,12 @@ typedef enum { struct teco_machine_main_t { teco_machine_t parent; + /** currently executed macro */ + const gchar *macro; /** Program counter, i.e. pointer to the next character in the current macro frame */ gsize macro_pc; + /** possibly preliminary end of macro */ + gsize stop_pos; struct teco_machine_main_flags_t { teco_mode_t mode : 8; @@ -550,6 +555,71 @@ gboolean teco_machine_main_eval_at(teco_machine_main_t *ctx); gboolean teco_machine_main_step(teco_machine_main_t *ctx, const gchar *macro, gsize stop_pos, GError **error); +#if __has_attribute(musttail) + +/** + * Transition from a teco_machine_main input_cb() to another state. + * + * Instead of returning to the caller, this can tail call, + * which will often be optimized to direct jumps, turning + * state transitions into "threaded" code. + * In some macros this achieves up to 18% speedup. + * + * On the other hand, this works only from functions with + * compatible signatures (practically only from + * teco_state_t::input_cb()). + * When this optimization is used, other function calls + * may no longer be tail-call optimized. + * + * @see teco_machine_main_step + * @see teco_machine_input + */ +#define TECO_RETURN(CTX, STATE, ERROR) G_STMT_START { \ + teco_machine_main_t *const __ctx = (CTX); \ + teco_state_t *const __state = (STATE); \ + GError **const __error = (ERROR); \ + \ + if (!__state) \ + return NULL; \ + \ + if (G_UNLIKELY(teco_interface_is_interrupted())) { \ + teco_error_interrupted_set(__error); \ + return NULL; \ + } \ + \ + if (!teco_memory_check(0, __error)) \ + return NULL; \ + \ + if (__ctx->macro_pc >= __ctx->stop_pos) \ + return __state; /* terminate tail calls */ \ + \ + if (__state != __ctx->parent.current) { \ + if (__ctx->parent.must_undo) \ + teco_undo_ptr(__ctx->parent.current); \ + __ctx->parent.current = __state; \ + \ + if (__ctx->parent.current->initial_cb && \ + !__ctx->parent.current->initial_cb(&__ctx->parent, __error)) \ + return NULL; \ + } \ + \ + gunichar chr = g_utf8_get_char(__ctx->macro+__ctx->macro_pc); \ + __ctx->macro_pc = g_utf8_next_char(__ctx->macro+__ctx->macro_pc) - __ctx->macro; \ + \ + /* the caller's signature will not line up exactly with input_cb */ \ + typedef teco_state_t *(*caller_t)(teco_machine_main_t *, gunichar, GError **); \ + __attribute__((musttail)) return ((caller_t)__state->input_cb)(__ctx, chr, __error); \ +} G_STMT_END + +#else /* __has_attribute(musttail) */ + +/* + * This will effectively return into the teco_machine_main_step() loop. + */ +#define TECO_RETURN(CTX, STATE, ERROR) return (STATE) + +#endif /* !__has_attribute(musttail) */ + gboolean teco_execute_macro(const gchar *macro, gsize macro_len, teco_qreg_table_t *qreg_table_locals, GError **error); gboolean teco_execute_file(const gchar *filename, teco_qreg_table_t *qreg_table_locals, GError **error); |
