From 3d852158bc8c2b795000ad5edddbbd7f088cc2f3 Mon Sep 17 00:00:00 2001 From: Brendan Punsky Date: Fri, 26 Jun 2026 04:22:03 -0400 Subject: [PATCH] rexcode/x86: generate the encode-recipe table (drop the @init interim) Move ENCODE_RECIPES from a startup-built (@init) static table in the library to a generated, #loaded table like every other one. The tablegen runs form_to_recipe over every form, serializes to x86.encode_recipes.bin, and the loader #loads it -- no @init, no allocation; the forms remain the single source of truth and the recipe is derived from them. - tablegen/gen.odin: ENCODE_RECIPES added to the BLOBS manifest (which drives both the Stage B serializer and the loader's #load); emit the generated-package global plus an @init that fills it from ENCODE_FORMS via lib.form_to_recipe. - encoder_recipe.odin: drop the @init, the static storage, and the ENCODE_RECIPES declaration. form_to_recipe stays -- now solely the tablegen-time derivation. - tables.odin, generated/{encode_tables,writer}.odin: regenerated. - tables/x86.encode_recipes.bin: the generated blob (28260 bytes = 2355 x 12). No behavior change: the recipes are identical to the @init ones. gen / builders / check / test / idempotent green (2282 cases, 22 byte-stable artifacts); encode perf unchanged. --- core/rexcode/isa/x86/encoder_recipe.odin | 28 ++++---------- core/rexcode/isa/x86/tablegen/gen.odin | 17 ++++++++- .../x86/tablegen/generated/encode_tables.odin | 8 ++++ .../isa/x86/tablegen/generated/writer.odin | 1 + core/rexcode/isa/x86/tables.odin | 35 +++++++++--------- .../isa/x86/tables/x86.encode_recipes.bin | Bin 0 -> 28260 bytes 6 files changed, 49 insertions(+), 40 deletions(-) create mode 100644 core/rexcode/isa/x86/tables/x86.encode_recipes.bin diff --git a/core/rexcode/isa/x86/encoder_recipe.odin b/core/rexcode/isa/x86/encoder_recipe.odin index 350bc39ed..b63878606 100644 --- a/core/rexcode/isa/x86/encoder_recipe.odin +++ b/core/rexcode/isa/x86/encoder_recipe.odin @@ -18,10 +18,9 @@ package rexcode_x86 // marked `eligible = false` and falls back to the existing interpreter, which // stays the source of truth for correctness. // -// NOTE(interim): ENCODE_RECIPES is currently built at startup from the #loaded -// ENCODE_FORMS (static storage, no heap). Once the fast path is validated this -// moves into the table generator -- serialized and #loaded like every other -// table, with no @init. +// ENCODE_RECIPES is produced by the tablegen (form_to_recipe over every form) +// and #loaded from x86.encode_recipes.bin like every other table -- see the note +// by form_to_recipe below. Form_Recipe :: struct { prefix: u8, // mandatory legacy prefix emitted before REX (0 = none) @@ -121,23 +120,10 @@ form_to_recipe :: proc "contextless" (enc: ^Encoding) -> (r: Form_Recipe) { return } -// ----------------------------------------------------------------------------- -// Interim recipe table: built once at startup from the #loaded forms into static -// storage (no heap). ENCODE_RECIPES is parallel to ENCODE_FORMS. -// ----------------------------------------------------------------------------- - -@(private) ENCODE_RECIPE_CAP :: 4096 -@(private) encode_recipes_storage: [ENCODE_RECIPE_CAP]Form_Recipe -ENCODE_RECIPES: []Form_Recipe - -@(init) -build_encode_recipes :: proc "contextless" () { - n := min(len(ENCODE_FORMS), ENCODE_RECIPE_CAP) - for i in 0.. bool { diff --git a/core/rexcode/isa/x86/tablegen/gen.odin b/core/rexcode/isa/x86/tablegen/gen.odin index 0a9ccc038..65f38a3ea 100644 --- a/core/rexcode/isa/x86/tablegen/gen.odin +++ b/core/rexcode/isa/x86/tablegen/gen.odin @@ -44,8 +44,9 @@ Blob :: struct { global, file, typ: string } @(rodata) BLOBS := [?]Blob{ - {"ENCODE_FORMS", "x86.encode_forms.bin", "Encoding"}, - {"ENCODE_RUNS", "x86.encode_runs.bin", "Encode_Run"}, + {"ENCODE_FORMS", "x86.encode_forms.bin", "Encoding"}, + {"ENCODE_RUNS", "x86.encode_runs.bin", "Encode_Run"}, + {"ENCODE_RECIPES", "x86.encode_recipes.bin", "Form_Recipe"}, {"MODRM_TABLE", "x86.modrm.bin", "ModRM_Info"}, {"SIB_TABLE", "x86.sib.bin", "SIB_Info"}, {"LEGACY_DECODE_ENTRIES", "x86.legacy.bin", "Decode_Entry"}, @@ -123,6 +124,18 @@ emit_encode_tables :: proc() { start += n } strings.write_string(&sb, "}\n") + + // Emit descriptor per form: the flattened recipe the encoder replays, derived + // from the forms via the library's form_to_recipe (single source of truth). + // Filled here at startup and serialized to x86.encode_recipes.bin; the loader + // #loads it, so the library itself needs no @init. + strings.write_string(&sb, "\n// Emit descriptor per form (derived from ENCODE_FORMS via lib.form_to_recipe).\n") + fmt.sbprintfln(&sb, "ENCODE_RECIPES: [%d]lib.Form_Recipe", total_forms()) + strings.write_string(&sb, "@(init) _fill_recipes :: proc \"contextless\" () {\n") + strings.write_string(&sb, "\tfor i in 0 ..< len(ENCODE_FORMS) {\n") + strings.write_string(&sb, "\t\tENCODE_RECIPES[i] = lib.form_to_recipe(&ENCODE_FORMS[i])\n") + strings.write_string(&sb, "\t}\n}\n") + emit_file(DIR_GEN + "encode_tables.odin", &sb) } diff --git a/core/rexcode/isa/x86/tablegen/generated/encode_tables.odin b/core/rexcode/isa/x86/tablegen/generated/encode_tables.odin index 11d45ce67..c743f7d0a 100644 --- a/core/rexcode/isa/x86/tablegen/generated/encode_tables.odin +++ b/core/rexcode/isa/x86/tablegen/generated/encode_tables.odin @@ -4720,3 +4720,11 @@ ENCODE_RUNS := [lib.Mnemonic]lib.Encode_Run{ .RDRAND = { 2349, 3}, .RDSEED = { 2352, 3}, } + +// Emit descriptor per form (derived from ENCODE_FORMS via lib.form_to_recipe). +ENCODE_RECIPES: [2355]lib.Form_Recipe +@(init) _fill_recipes :: proc "contextless" () { + for i in 0 ..< len(ENCODE_FORMS) { + ENCODE_RECIPES[i] = lib.form_to_recipe(&ENCODE_FORMS[i]) + } +} diff --git a/core/rexcode/isa/x86/tablegen/generated/writer.odin b/core/rexcode/isa/x86/tablegen/generated/writer.odin index fec97ef84..7a91efc83 100644 --- a/core/rexcode/isa/x86/tablegen/generated/writer.odin +++ b/core/rexcode/isa/x86/tablegen/generated/writer.odin @@ -24,6 +24,7 @@ w :: proc(file: string, data: []u8) { main :: proc() { w(TABLES + "x86.encode_forms.bin", raw(&ENCODE_FORMS, size_of(ENCODE_FORMS))) w(TABLES + "x86.encode_runs.bin", raw(&ENCODE_RUNS, size_of(ENCODE_RUNS))) + w(TABLES + "x86.encode_recipes.bin", raw(&ENCODE_RECIPES, size_of(ENCODE_RECIPES))) w(TABLES + "x86.modrm.bin", raw(&MODRM_TABLE, size_of(MODRM_TABLE))) w(TABLES + "x86.sib.bin", raw(&SIB_TABLE, size_of(SIB_TABLE))) w(TABLES + "x86.legacy.bin", raw(&LEGACY_DECODE_ENTRIES, size_of(LEGACY_DECODE_ENTRIES))) diff --git a/core/rexcode/isa/x86/tables.odin b/core/rexcode/isa/x86/tables.odin index 06aae9829..32a5c7af1 100644 --- a/core/rexcode/isa/x86/tables.odin +++ b/core/rexcode/isa/x86/tables.odin @@ -76,23 +76,24 @@ Decode_Index :: struct { // Loaded tables (rodata, embedded from tables/*.bin at compile time) // ----------------------------------------------------------------------------- -@(rodata) ENCODE_FORMS := #load("tables/x86.encode_forms.bin", []Encoding) -@(rodata) ENCODE_RUNS := #load("tables/x86.encode_runs.bin", []Encode_Run) -@(rodata) MODRM_TABLE := #load("tables/x86.modrm.bin", []ModRM_Info) -@(rodata) SIB_TABLE := #load("tables/x86.sib.bin", []SIB_Info) -@(rodata) LEGACY_DECODE_ENTRIES := #load("tables/x86.legacy.bin", []Decode_Entry) -@(rodata) VEX_DECODE_ENTRIES := #load("tables/x86.vex.bin", []VEX_Decode_Entry) -@(rodata) EVEX_DECODE_ENTRIES := #load("tables/x86.evex.bin", []VEX_Decode_Entry) -@(rodata) DECODE_INDEX_LEGACY := #load("tables/x86.idx_legacy.bin", []Decode_Index) -@(rodata) DECODE_INDEX_ESC_0F := #load("tables/x86.idx_0f.bin", []Decode_Index) -@(rodata) DECODE_INDEX_ESC_0F38 := #load("tables/x86.idx_0f38.bin", []Decode_Index) -@(rodata) DECODE_INDEX_ESC_0F3A := #load("tables/x86.idx_0f3a.bin", []Decode_Index) -@(rodata) VEX_INDEX_0F := #load("tables/x86.vex_idx_0f.bin", []Decode_Index) -@(rodata) VEX_INDEX_0F38 := #load("tables/x86.vex_idx_0f38.bin", []Decode_Index) -@(rodata) VEX_INDEX_0F3A := #load("tables/x86.vex_idx_0f3a.bin", []Decode_Index) -@(rodata) EVEX_INDEX_0F := #load("tables/x86.evex_idx_0f.bin", []Decode_Index) -@(rodata) EVEX_INDEX_0F38 := #load("tables/x86.evex_idx_0f38.bin", []Decode_Index) -@(rodata) EVEX_INDEX_0F3A := #load("tables/x86.evex_idx_0f3a.bin", []Decode_Index) +@(rodata) ENCODE_FORMS := #load("tables/x86.encode_forms.bin", []Encoding) +@(rodata) ENCODE_RUNS := #load("tables/x86.encode_runs.bin", []Encode_Run) +@(rodata) ENCODE_RECIPES := #load("tables/x86.encode_recipes.bin", []Form_Recipe) +@(rodata) MODRM_TABLE := #load("tables/x86.modrm.bin", []ModRM_Info) +@(rodata) SIB_TABLE := #load("tables/x86.sib.bin", []SIB_Info) +@(rodata) LEGACY_DECODE_ENTRIES := #load("tables/x86.legacy.bin", []Decode_Entry) +@(rodata) VEX_DECODE_ENTRIES := #load("tables/x86.vex.bin", []VEX_Decode_Entry) +@(rodata) EVEX_DECODE_ENTRIES := #load("tables/x86.evex.bin", []VEX_Decode_Entry) +@(rodata) DECODE_INDEX_LEGACY := #load("tables/x86.idx_legacy.bin", []Decode_Index) +@(rodata) DECODE_INDEX_ESC_0F := #load("tables/x86.idx_0f.bin", []Decode_Index) +@(rodata) DECODE_INDEX_ESC_0F38 := #load("tables/x86.idx_0f38.bin", []Decode_Index) +@(rodata) DECODE_INDEX_ESC_0F3A := #load("tables/x86.idx_0f3a.bin", []Decode_Index) +@(rodata) VEX_INDEX_0F := #load("tables/x86.vex_idx_0f.bin", []Decode_Index) +@(rodata) VEX_INDEX_0F38 := #load("tables/x86.vex_idx_0f38.bin", []Decode_Index) +@(rodata) VEX_INDEX_0F3A := #load("tables/x86.vex_idx_0f3a.bin", []Decode_Index) +@(rodata) EVEX_INDEX_0F := #load("tables/x86.evex_idx_0f.bin", []Decode_Index) +@(rodata) EVEX_INDEX_0F38 := #load("tables/x86.evex_idx_0f38.bin", []Decode_Index) +@(rodata) EVEX_INDEX_0F3A := #load("tables/x86.evex_idx_0f3a.bin", []Decode_Index) // ----------------------------------------------------------------------------- // Accessors diff --git a/core/rexcode/isa/x86/tables/x86.encode_recipes.bin b/core/rexcode/isa/x86/tables/x86.encode_recipes.bin new file mode 100644 index 0000000000000000000000000000000000000000..630a3239d027f34cd01de7adf0e42dc61d7d1c61 GIT binary patch literal 28260 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 literal 0 HcmV?d00001