aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
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 /src
parent6aa97a68a85b83267de597bfb30b70c04b7de97b (diff)
Revert "optimize main state machine transitions with tail calls"HEADmaster-fmsbw-cimaster
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.
Diffstat (limited to 'src')
-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) {