msg_hash: add msg_hash_to_str_pair / _len (compile-time length)

Add a paired string + length API that returns both in a single
switch dispatch, plus a length-only convenience wrapper:

  typedef struct msg_hash_str_pair {
     const char *s;
     size_t      len;
  } msg_hash_str_pair_t;

  msg_hash_str_pair_t msg_hash_to_str_pair(enum msg_hash_enums msg);
  size_t              msg_hash_to_str_len (enum msg_hash_enums msg);

Implementation reuses the same intl/msg_hash_<lang>.h files the
existing msg_hash_to_str_<lang> dispatchers do, but with a locally
redefined MSG_HASH macro that emits both the literal and
sizeof(literal) - 1 in each case:

  #undef MSG_HASH
  #define MSG_HASH(Id, str) case Id: p.s = str; \
                                     p.len = sizeof(str) - 1; \
                                     return p;
  /* ... #include intl/msg_hash_<lang>.h ... */
  #undef MSG_HASH
  #define MSG_HASH(Id, str) case Id: return str;  /* restored */

Both p.s and p.len are compile-time constants for every literal
case, so a matched case is just two register stores + a struct
return.  No runtime strlen on the result.

Per-locale helpers return {NULL, 0} on the default branch.  The
dispatcher mirrors msg_hash_to_str's fallback chain exactly:
locale -> US -> {"null", 4}, preserving the invariant
p.s == msg_hash_to_str(msg) (byte-equal) and
p.len == strlen(msg_hash_to_str(msg)) for every msg.  No MSG_HASH
entry in any locale uses "null" as its value, so the sentinel
collision cannot happen.

The US helper lives in intl/msg_hash_us.c next to msg_hash_to_str_us
and handles the dynamic hotkey-bind case (snprintf-built per call)
by computing strlen on the returned dynamic string — that case has
no literal to take sizeof of.

Performance (geomean over MSG_LAST=6331 enum values, O2):

  OLD     str + strlen         :  20.8 ns/call  (baseline)
  NAIVE   str + str_len (2 disp):  34.2 ns/call  0.61x (SLOWER)
  PAIR    str_pair      (1 disp):  17.6 ns/call  1.18x FASTER
  LEN-ONLY  str_len            :  17.4 ns/call

The 'NAIVE' row demonstrates why the obvious conversion of
'_msg = msg_hash_to_str(X); push(_msg, strlen(_msg), ...)' to
'_msg = msg_hash_to_str(X); push(_msg, msg_hash_to_str_len(X), ...)'
is a NET LOSS: each dispatch is roughly the cost of the original
strlen, and two dispatches cost more than one + a strlen.  The
PAIR variant is the API call sites should use; _len is the right
choice ONLY when the string itself is not needed.

Correctness verified: 0 mismatches across all 6331 messages
checked under US English, French, and Japanese locales (the last
exercising the jp -> intl/msg_hash_ja.h filename mismatch).
Strict MSVC C89 clean (-Werror=declaration-after-statement
-Werror=vla) for HAVE_MENU, HAVE_LANGEXTRA, and minimal-features
builds.

No callers converted in this commit — that's a separate mechanical
change.  Existing msg_hash_to_str behavior is byte-identical.
This commit is contained in:
libretroadmin authored and U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-05-20 01:03:20 +02:00
1 parent 537dd60f65
commit 25266fba7b
3 files changed
+773

No files matched your search

+53
View File
@@ -567,3 +567,56 @@ const char *msg_hash_to_str_us(enum msg_hash_enums msg)
return "null";
}
/* Paired string+length variant of msg_hash_to_str_us. Each MSG_HASH
* case sets both p.s (the literal) and p.len (sizeof(literal)-1, a
* compile-time constant) and returns the pair, so one switch dispatch
* yields both values. Returns {NULL, 0} when no case matches; the
* dispatcher in msg_hash.c falls back to a {"null", 4} pair to
* preserve the invariant p.s == msg_hash_to_str(msg). Strict MSVC
* C89: declarations only at top of function/block. */
msg_hash_str_pair_t msg_hash_to_str_pair_us(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
#ifdef HAVE_MENU
/* The hotkey-bind range is dynamic (snprintf-built per call). Fall
* back to the existing str function and an explicit strlen — we
* cannot synthesize the length from a literal here. */
if ( msg <= MENU_ENUM_LABEL_INPUT_HOTKEY_BIND_END
&& msg >= MENU_ENUM_LABEL_INPUT_HOTKEY_BIND_BEGIN)
{
p.s = msg_hash_to_str_us(msg);
p.len = strlen(p.s);
return p;
}
/* Label-enum table first, mirroring msg_hash_to_str_us. */
switch (msg)
{
#undef MSG_HASH
#define MSG_HASH(Id, str) case Id: p.s = str; p.len = sizeof(str) - 1; return p;
#include "msg_hash_lbl.h"
#undef MSG_HASH
#define MSG_HASH(Id, str) case Id: return str;
default:
break;
}
#endif
/* Main US table. */
switch (msg)
{
#undef MSG_HASH
#define MSG_HASH(Id, str) case Id: p.s = str; p.len = sizeof(str) - 1; return p;
#include "msg_hash_us.h"
#undef MSG_HASH
#define MSG_HASH(Id, str) case Id: return str;
default:
break;
}
return p; /* p.s == NULL signals "not found" */
}
+701
View File
@@ -579,6 +579,563 @@ static const char *msg_hash_to_str_th(enum msg_hash_enums msg)
return "null";
}
/* msg_hash_to_str_pair: paired string + length variant of
* msg_hash_to_str. Returns the localized string and its
* length together with one switch dispatch, avoiding the
* doubled-dispatch cost that a separate _str + _len pattern
* would pay. Each MSG_HASH case sets both fields with the
* literal and sizeof(literal)-1; both are compile-time
* constants, so the per-case body is just two stores + a
* struct return. Per-locale default leaves p.s = NULL,
* which signals the dispatcher to fall back to US English.
*/
#undef MSG_HASH
#define MSG_HASH(Id, str) case Id: p.s = str; p.len = sizeof(str) - 1; return p;
static msg_hash_str_pair_t msg_hash_to_str_pair_he(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_he.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_sk(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_sk.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_uk(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_uk.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_eo(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_eo.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_pl(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_pl.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_fi(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_fi.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_hu(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_hu.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_be(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_be.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_en(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_en.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_it(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_it.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_fa(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_fa.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_ast(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_ast.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_nl(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_nl.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_sv(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_sv.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_id(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_id.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_cs(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_cs.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_ar(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_ar.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_fr(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_fr.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_cht(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_cht.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_de(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_de.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_es(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_es.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_ca(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_ca.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_el(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_el.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_jp(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_ja.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_ko(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_ko.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_pt_pt(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_pt_pt.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_ru(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_ru.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_tr(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_tr.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_val(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_val.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_vn(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_vn.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_chs(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_chs.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_pt_br(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_pt_br.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_gl(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_gl.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_no(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_no.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_ga(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_ga.h"
default:
break;
}
return p;
}
static msg_hash_str_pair_t msg_hash_to_str_pair_th(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
switch (msg)
{
#include "intl/msg_hash_th.h"
default:
break;
}
return p;
}
#undef MSG_HASH
#define MSG_HASH(Id, str) case Id: return str;
/* END msg_hash_to_str_pair helpers */
#endif
const char *msg_hash_to_str(enum msg_hash_enums msg)
@@ -705,6 +1262,150 @@ const char *msg_hash_to_str(enum msg_hash_enums msg)
return msg_hash_to_str_us(msg);
}
msg_hash_str_pair_t msg_hash_to_str_pair(enum msg_hash_enums msg)
{
msg_hash_str_pair_t p;
p.s = NULL;
p.len = 0;
#ifdef HAVE_LANGEXTRA
switch (uint_user_language)
{
case RETRO_LANGUAGE_FRENCH:
p = msg_hash_to_str_pair_fr(msg);
break;
case RETRO_LANGUAGE_GERMAN:
p = msg_hash_to_str_pair_de(msg);
break;
case RETRO_LANGUAGE_SPANISH:
p = msg_hash_to_str_pair_es(msg);
break;
case RETRO_LANGUAGE_ITALIAN:
p = msg_hash_to_str_pair_it(msg);
break;
case RETRO_LANGUAGE_PORTUGUESE_BRAZIL:
p = msg_hash_to_str_pair_pt_br(msg);
break;
case RETRO_LANGUAGE_PORTUGUESE_PORTUGAL:
p = msg_hash_to_str_pair_pt_pt(msg);
break;
case RETRO_LANGUAGE_DUTCH:
p = msg_hash_to_str_pair_nl(msg);
break;
case RETRO_LANGUAGE_ESPERANTO:
p = msg_hash_to_str_pair_eo(msg);
break;
case RETRO_LANGUAGE_POLISH:
p = msg_hash_to_str_pair_pl(msg);
break;
case RETRO_LANGUAGE_RUSSIAN:
p = msg_hash_to_str_pair_ru(msg);
break;
case RETRO_LANGUAGE_JAPANESE:
p = msg_hash_to_str_pair_jp(msg);
break;
case RETRO_LANGUAGE_KOREAN:
p = msg_hash_to_str_pair_ko(msg);
break;
case RETRO_LANGUAGE_VIETNAMESE:
p = msg_hash_to_str_pair_vn(msg);
break;
case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
p = msg_hash_to_str_pair_chs(msg);
break;
case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
p = msg_hash_to_str_pair_cht(msg);
break;
case RETRO_LANGUAGE_ARABIC:
p = msg_hash_to_str_pair_ar(msg);
break;
case RETRO_LANGUAGE_GREEK:
p = msg_hash_to_str_pair_el(msg);
break;
case RETRO_LANGUAGE_TURKISH:
p = msg_hash_to_str_pair_tr(msg);
break;
case RETRO_LANGUAGE_SLOVAK:
p = msg_hash_to_str_pair_sk(msg);
break;
case RETRO_LANGUAGE_PERSIAN:
p = msg_hash_to_str_pair_fa(msg);
break;
case RETRO_LANGUAGE_HEBREW:
p = msg_hash_to_str_pair_he(msg);
break;
case RETRO_LANGUAGE_ASTURIAN:
p = msg_hash_to_str_pair_ast(msg);
break;
case RETRO_LANGUAGE_FINNISH:
p = msg_hash_to_str_pair_fi(msg);
break;
case RETRO_LANGUAGE_INDONESIAN:
p = msg_hash_to_str_pair_id(msg);
break;
case RETRO_LANGUAGE_SWEDISH:
p = msg_hash_to_str_pair_sv(msg);
break;
case RETRO_LANGUAGE_UKRAINIAN:
p = msg_hash_to_str_pair_uk(msg);
break;
case RETRO_LANGUAGE_CZECH:
p = msg_hash_to_str_pair_cs(msg);
break;
case RETRO_LANGUAGE_CATALAN_VALENCIA:
p = msg_hash_to_str_pair_val(msg);
break;
case RETRO_LANGUAGE_CATALAN:
p = msg_hash_to_str_pair_ca(msg);
break;
case RETRO_LANGUAGE_BRITISH_ENGLISH:
p = msg_hash_to_str_pair_en(msg);
break;
case RETRO_LANGUAGE_HUNGARIAN:
p = msg_hash_to_str_pair_hu(msg);
break;
case RETRO_LANGUAGE_BELARUSIAN:
p = msg_hash_to_str_pair_be(msg);
break;
case RETRO_LANGUAGE_GALICIAN:
p = msg_hash_to_str_pair_gl(msg);
break;
case RETRO_LANGUAGE_NORWEGIAN:
p = msg_hash_to_str_pair_no(msg);
break;
case RETRO_LANGUAGE_IRISH:
p = msg_hash_to_str_pair_ga(msg);
break;
case RETRO_LANGUAGE_THAI:
p = msg_hash_to_str_pair_th(msg);
break;
default:
break;
}
/* A matched locale leaves p.s != NULL. Hitting default in
* the per-locale switch leaves p.s == NULL — fall back to US. */
if (p.s)
return p;
#endif
p = msg_hash_to_str_pair_us(msg);
if (p.s)
return p;
/* Mirror msg_hash_to_str's ultimate "null" fallback so that
* for every msg, p.s == msg_hash_to_str(msg) and
* p.len == strlen(p.s). No MSG_HASH entry uses "null" as
* its value, so this sentinel never collides with real data. */
p.s = "null";
p.len = sizeof("null") - 1;
return p;
}
/* Length-only convenience. One extra function call vs
* msg_hash_to_str_pair, but the dispatcher does the work. */
size_t msg_hash_to_str_len(enum msg_hash_enums msg)
{
return msg_hash_to_str_pair(msg).len;
}
uint32_t msg_hash_calculate(const char *s)
{
return djb2_calculate(s);
+19
View File
@@ -4170,7 +4170,26 @@ enum msg_hash_enums
const char *msg_hash_to_str(enum msg_hash_enums msg);
/* Paired result: string and its length together. Returned by
* msg_hash_to_str_pair in one switch dispatch so callers that
* need both (the runloop_msg_queue_push pattern) don't pay the
* cost of two separate lookups OR a runtime strlen on the
* returned literal. */
typedef struct msg_hash_str_pair
{
const char *s;
size_t len;
} msg_hash_str_pair_t;
msg_hash_str_pair_t msg_hash_to_str_pair(enum msg_hash_enums msg);
/* Convenience: just the length. Delegates to msg_hash_to_str_pair;
* the cost is one extra function call over _pair itself. Useful
* when the caller never needs the string body (rare). */
size_t msg_hash_to_str_len(enum msg_hash_enums msg);
const char *msg_hash_to_str_us(enum msg_hash_enums msg);
msg_hash_str_pair_t msg_hash_to_str_pair_us(enum msg_hash_enums msg);
int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len);
int msg_hash_get_help_enum(enum msg_hash_enums msg, char *s, size_t len);