aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <rhaberkorn@fmsbw.de>2026-08-23 22:08:16 +0200
committerRobin Haberkorn <rhaberkorn@fmsbw.de>2026-08-23 22:08:16 +0200
commit33bab281e27cf866e77fb3bba6e37f5a5edfef03 (patch)
tree8332298257ccabe511efbc6deeec0de25672fe10
parent6aa97a68a85b83267de597bfb30b70c04b7de97b (diff)
Revert "optimize main state machine transitions with tail calls"
For yet unknown reasons this does not improve performance on GCC 16 even though it also supports `__attribute__((musttail))`. As long as I do not understand why, I don't want to risk any unnecessary problems. I have reason to believe that the tail-call optimized version still has subtle bugs. See also the "tail-calls" branch. This reverts commit 2e097cec409182c3cb39b74489387480bf8a278f.
-rw-r--r--src/core-commands.c64
-rw-r--r--src/goto-commands.c26
-rw-r--r--src/parser.c11
-rw-r--r--src/parser.h70
-rw-r--r--src/qreg-commands.c5
-rw-r--r--src/stdio-commands.c4
6 files changed, 50 insertions, 130 deletions
diff --git a/src/core-commands.c b/src/core-commands.c
index d01cbb9..612d980 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -763,7 +763,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_error_modifier_set(error, cmd);
return NULL;
}
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
}
switch (chr) {
@@ -792,7 +792,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
}
if (ctx->flags.mode == TECO_MODE_NORMAL)
teco_expressions_add_digit(chr, ctx->qreg_table_locals->radix);
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
case '*':
/*
@@ -804,7 +804,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
*/
if (teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) == 1 &&
teco_cmdline_ssm(SCI_GETCHARAT, 0, 0) == '*')
- TECO_RETURN(ctx, &teco_state_save_cmdline, error);
+ return &teco_state_save_cmdline;
/* treat as an operator */
break;
@@ -819,7 +819,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
if (ctx->parent.must_undo)
teco_undo_gint(ctx->nest_level);
ctx->nest_level++;
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
case '>':
if (ctx->flags.modifier_at) {
@@ -838,7 +838,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_undo_gint(ctx->nest_level);
ctx->nest_level--;
}
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
/*
* Control Structures (conditionals)
@@ -856,9 +856,9 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
else if (ctx->flags.mode == TECO_MODE_NORMAL)
/* skip to end of conditional; skip ELSE-part */
ctx->flags.mode = TECO_MODE_PARSE_ONLY_COND;
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
- case '\'': TECO_RETURN(ctx, teco_state_endcond(ctx, "'", error), error);
+ case '\'': return teco_state_endcond(ctx, "'", error);
/*
* Word movement and deletion commands.
@@ -871,13 +871,13 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
* but should accept only one colon modifier.
*/
case 'w':
- case 'W': TECO_RETURN(ctx, teco_state_start_words(ctx, "W", 1, error), error);
+ case 'W': return teco_state_start_words(ctx, "W", 1, error);
case 'p':
- case 'P': TECO_RETURN(ctx, teco_state_start_words(ctx, "P", -1, error), error);
+ case 'P': return teco_state_start_words(ctx, "P", -1, error);
case 'v':
- case 'V': TECO_RETURN(ctx, teco_state_start_delete_words(ctx, "V", 1, error), error);
+ case 'V': return teco_state_start_delete_words(ctx, "V", 1, error);
case 'y':
- case 'Y': TECO_RETURN(ctx, teco_state_start_delete_words(ctx, "Y", -1, error), error);
+ case 'Y': return teco_state_start_delete_words(ctx, "Y", -1, error);
/*
* Modifiers
@@ -896,11 +896,11 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
if (ctx->parent.must_undo)
teco_undo_flags(ctx->flags);
ctx->flags.modifier_at = TRUE;
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
case ':':
if (ctx->flags.mode > TECO_MODE_NORMAL)
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
if (ctx->flags.modifier_colon >= 2) {
teco_error_modifier_set(error, ":");
return NULL;
@@ -908,21 +908,18 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
if (ctx->parent.must_undo)
teco_undo_flags(ctx->flags);
ctx->flags.modifier_colon++;
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
default:
/*
* <CTRL/x> commands implemented in teco_state_control
*/
- if (TECO_IS_CTL(chr)) {
- teco_state_t *next = teco_state_control_input(ctx, TECO_CTL_ECHO(chr), error);
- TECO_RETURN(ctx, next, error);
- }
+ if (TECO_IS_CTL(chr))
+ return teco_state_control_input(ctx, TECO_CTL_ECHO(chr), error);
}
- teco_state_t *next = teco_machine_main_transition_input(ctx, transitions, G_N_ELEMENTS(transitions),
- teco_ascii_toupper(chr), error);
- TECO_RETURN(ctx, next, error);
+ return teco_machine_main_transition_input(ctx, transitions, G_N_ELEMENTS(transitions),
+ teco_ascii_toupper(chr), error);
}
TECO_DEFINE_STATE_START(teco_state_start,
@@ -1091,12 +1088,11 @@ teco_state_fcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
};
switch (chr) {
- case '"': TECO_RETURN(ctx, teco_state_endcond(ctx, "F\"", error), error);
+ case '"': return teco_state_endcond(ctx, "F\"", error);
}
- teco_state_t *next = teco_machine_main_transition_input(ctx, transitions, G_N_ELEMENTS(transitions),
- teco_ascii_toupper(chr), error);
- TECO_RETURN(ctx, next, error);
+ return teco_machine_main_transition_input(ctx, transitions, G_N_ELEMENTS(transitions),
+ teco_ascii_toupper(chr), error);
}
TECO_DEFINE_STATE_COMMAND(teco_state_fcommand,
@@ -1305,7 +1301,7 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er
ctx->flags.mode = TECO_MODE_PARSE_ONLY_COND;
}
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
}
TECO_DEFINE_STATE_COMMAND(teco_state_condcommand,
@@ -1741,9 +1737,8 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
* Currently you get error messages like 'Syntax error "F"' for ^F.
* The easiest way around would be g_prefix_error(error, "Control command");
*/
- teco_state_t *next = teco_machine_main_transition_input(ctx, transitions, G_N_ELEMENTS(transitions),
- teco_ascii_toupper(chr), error);
- TECO_RETURN(ctx, next, error);
+ return teco_machine_main_transition_input(ctx, transitions, G_N_ELEMENTS(transitions),
+ teco_ascii_toupper(chr), error);
}
TECO_DEFINE_STATE_COMMAND(teco_state_control,
@@ -1756,7 +1751,7 @@ teco_state_ascii_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
if (ctx->flags.mode == TECO_MODE_NORMAL)
teco_expressions_push(chr);
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
}
/*$ ^^ ^^c
@@ -1941,7 +1936,7 @@ teco_state_ctlc_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
switch (chr) {
case TECO_CTL_KEY('C'): return teco_state_ctlc_control_input(ctx, 'C', error);
- case '^': TECO_RETURN(ctx, &teco_state_ctlc_control, error);
+ case '^': return &teco_state_ctlc_control;
}
return ctx->flags.mode > TECO_MODE_NORMAL
@@ -1994,7 +1989,7 @@ teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, GError **e
*/
if (chr == 'c' || chr == 'C') {
if (ctx->flags.mode > TECO_MODE_NORMAL)
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
if (teco_undo_enabled) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
@@ -2958,9 +2953,8 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
/*
* FIXME: Should we return a special syntax error in case of failure?
*/
- teco_state_t *next = teco_machine_main_transition_input(ctx, transitions, G_N_ELEMENTS(transitions),
- teco_ascii_toupper(chr), error);
- TECO_RETURN(ctx, next, error);
+ return teco_machine_main_transition_input(ctx, transitions, G_N_ELEMENTS(transitions),
+ teco_ascii_toupper(chr), error);
}
TECO_DEFINE_STATE_COMMAND(teco_state_ecommand,
diff --git a/src/goto-commands.c b/src/goto-commands.c
index 72ac80d..a9ff3c2 100644
--- a/src/goto-commands.c
+++ b/src/goto-commands.c
@@ -61,8 +61,8 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
if (!ctx->goto_label.len) {
switch (chr) {
- case '*': TECO_RETURN(ctx, &teco_state_blockcomment, error); /* `!*` */
- case '!': TECO_RETURN(ctx, &teco_state_eolcomment, error); /* `!!` */
+ case '*': return &teco_state_blockcomment; /* `!*` */
+ case '!': return &teco_state_eolcomment; /* `!!` */
}
}
@@ -97,7 +97,7 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_string_clear(&ctx->goto_label);
memset(&ctx->goto_label, 0, sizeof(ctx->goto_label));
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
}
/*
@@ -109,7 +109,7 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
if (ctx->parent.must_undo)
undo__teco_string_truncate(&ctx->goto_label, ctx->goto_label.len);
teco_string_append_wc(&ctx->goto_label, chr);
- TECO_RETURN(ctx, &teco_state_label, error);
+ return &teco_state_label;
}
TECO_DEFINE_STATE(teco_state_label,
@@ -244,34 +244,34 @@ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_goto,
)
static teco_state_t *
-teco_state_blockcomment_star_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+teco_state_blockcomment_star_input(teco_machine_t *ctx, gunichar chr, GError **error)
{
- TECO_RETURN(ctx, chr == '!' ? &teco_state_start : &teco_state_blockcomment, error);
+ return chr == '!' ? &teco_state_start : &teco_state_blockcomment;
}
static TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment_star,
- .input_cb = (teco_state_input_cb_t)teco_state_blockcomment_star_input
+ .input_cb = teco_state_blockcomment_star_input
);
static teco_state_t *
-teco_state_blockcomment_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+teco_state_blockcomment_input(teco_machine_t *ctx, gunichar chr, GError **error)
{
- TECO_RETURN(ctx, chr == '*' ? &teco_state_blockcomment_star : &teco_state_blockcomment, error);
+ return chr == '*' ? &teco_state_blockcomment_star : &teco_state_blockcomment;
}
static TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment,
- .input_cb = (teco_state_input_cb_t)teco_state_blockcomment_input
+ .input_cb = teco_state_blockcomment_input
);
/*
* `!!` line comments are inspired by TECO-64.
*/
static teco_state_t *
-teco_state_eolcomment_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+teco_state_eolcomment_input(teco_machine_t *ctx, gunichar chr, GError **error)
{
- TECO_RETURN(ctx, chr == '\n' ? &teco_state_start : &teco_state_eolcomment, error);
+ return chr == '\n' ? &teco_state_start : &teco_state_eolcomment;
}
static TECO_DEFINE_STATE_COMMENT(teco_state_eolcomment,
- .input_cb = (teco_state_input_cb_t)teco_state_eolcomment_input
+ .input_cb = teco_state_eolcomment_input
);
diff --git a/src/parser.c b/src/parser.c
index 8ec051e..8b02764 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -104,9 +104,6 @@ teco_machine_main_step(teco_machine_main_t *ctx, const gchar *macro, gsize stop_
{
gsize last_pc = 0;
- ctx->macro = macro;
- ctx->stop_pos = stop_pos;
-
while (ctx->macro_pc < stop_pos) {
last_pc = ctx->macro_pc;
@@ -1094,7 +1091,7 @@ teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar chr, GError **e
* as allowing whitespace escape_chars is harmful.
*/
if (ctx->flags.modifier_at && teco_is_noop(chr))
- TECO_RETURN(ctx, current, error);
+ return current;
/*
* String termination handling
@@ -1109,7 +1106,7 @@ teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar chr, GError **e
if (ctx->parent.must_undo)
teco_undo_gunichar(ctx->expectstring.machine.escape_char);
ctx->expectstring.machine.escape_char = g_unichar_toupper(chr);
- TECO_RETURN(ctx, current, error);
+ return current;
}
/*
@@ -1180,7 +1177,7 @@ teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar chr, GError **e
if (ctx->parent.must_undo)
teco_undo_gsize(ctx->expectstring.insert_len);
ctx->expectstring.insert_len = 0;
- TECO_RETURN(ctx, next, error);
+ return next;
}
/*
@@ -1216,7 +1213,7 @@ teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar chr, GError **e
teco_undo_gsize(ctx->expectstring.insert_len);
ctx->expectstring.insert_len += ctx->expectstring.string.len - old_len;
- TECO_RETURN(ctx, current, error);
+ return current;
}
gboolean
diff --git a/src/parser.h b/src/parser.h
index 75a3432..0c389cc 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -25,7 +25,6 @@
#include "goto.h"
#include "undo.h"
#include "qreg.h"
-#include "memory.h"
#include "lexer.h"
/*
@@ -482,12 +481,8 @@ 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;
@@ -555,71 +550,6 @@ 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);
diff --git a/src/qreg-commands.c b/src/qreg-commands.c
index d7b0a10..a6390a1 100644
--- a/src/qreg-commands.c
+++ b/src/qreg-commands.c
@@ -62,7 +62,7 @@ teco_state_expectqreg_input(teco_machine_main_t *ctx, gunichar chr, GError **err
case TECO_MACHINE_QREGSPEC_ERROR:
return NULL;
case TECO_MACHINE_QREGSPEC_MORE:
- TECO_RETURN(ctx, current, error);
+ return current;
case TECO_MACHINE_QREGSPEC_DONE:
break;
}
@@ -72,8 +72,7 @@ teco_state_expectqreg_input(teco_machine_main_t *ctx, gunichar chr, GError **err
* states. This means, it must usually be reset manually in got_register_cb() via:
* teco_state_expectqreg_reset(ctx);
*/
- teco_state_t *next = current->expectqreg.got_register_cb(ctx, qreg, table, error);
- TECO_RETURN(ctx, next, error);
+ return current->expectqreg.got_register_cb(ctx, qreg, table, error);
}
static teco_state_t *
diff --git a/src/stdio-commands.c b/src/stdio-commands.c
index 94da3c7..34179c3 100644
--- a/src/stdio-commands.c
+++ b/src/stdio-commands.c
@@ -139,7 +139,7 @@ static teco_state_t *
teco_state_print_decimal_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
if (chr == '=')
- TECO_RETURN(ctx, &teco_state_print_octal, error);
+ return &teco_state_print_octal;
if (ctx->flags.mode == TECO_MODE_NORMAL) {
if (G_LIKELY(!is_executing) && !teco_print(ctx, 10, error))
@@ -197,7 +197,7 @@ teco_state_print_octal_input(teco_machine_main_t *ctx, gunichar chr, GError **er
return NULL;
teco_expressions_pop_num(0);
}
- TECO_RETURN(ctx, &teco_state_start, error);
+ return &teco_state_start;
}
if (ctx->flags.mode == TECO_MODE_NORMAL) {