feat(relinker): --to-intel lowers SSE4a sites to same-length jumps into an appended stub section

EXTRQ, INSERTQ, MOVNTSS and MOVNTSD are rewritten at relink time: same-length in-place forms
where they exist, otherwise a jmp rel32 into an appended executable section holding a
scratch-free SSE body and a jump back, so no code shifts and no RIP-relative or jump-table
operand needs adjusting. Decoder fixes for POPCNT and 0F BB (BTC), codegen and elfpatcher
tests, README and technical-debt notes. The runtime illegal-instruction trap stays as a
fallback.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0177gthrWEBBfBu5xJDXgBfo
This commit is contained in:
devilismyfriendandClaude Fable 5.1 committed 2026-09-26 19:00:39 -07:00
1 parent af3c2024ed
commit 01c4e3edf7
28 files changed
+1515 -77

No files matched your search

@@ -1,6 +1,7 @@
#ifndef CODEGEN_CODEGENTYPES_HPP
#define CODEGEN_CODEGENTYPES_HPP
#include <codegen/x86/Amd64OnlySubstitutionTypes.hpp>
#include <domain/Types.hpp>
#include <cstdint>
#include <vector>
@@ -28,9 +29,20 @@ struct RewriteResult {
std::vector<AddressAdjustment> Adjustments;
};
struct TrampolineSite {
Domain::FileByteOffset Offset;
Domain::VirtualAddress Address;
std::size_t Length;
std::vector<std::uint8_t> OriginalBytes;
std::vector<std::uint8_t> Body;
std::size_t ReturnBranchOffset;
};
struct ConvertResult {
std::vector<std::uint8_t> Bytes;
std::size_t ReplacedCount;
std::vector<TrampolineSite> Trampolines;
std::vector<Amd64OnlySubstitutionReport> Reports;
};
enum class ControlFlowKind : std::uint8_t {
@@ -17,14 +17,39 @@ struct Entry {
inline constexpr std::uint8_t k##name##Bytes[] = {__VA_ARGS__}; \
inline constexpr Entry k##name = {#name, k##name##Bytes, sizeof(k##name##Bytes)};
AMD64_STUB(Monitorx, 0x0F, 0x01, 0xFA)
AMD64_STUB(Mwaitx, 0x0F, 0x01, 0xFB)
AMD64_STUB(Clzero, 0x0F, 0x01, 0xFC)
AMD64_STUB(Rdpru, 0x0F, 0x01, 0xFD)
AMD64_STUB(Mcommit, 0xF3, 0x0F, 0x01, 0xFA)
AMD64_STUB(JmpRel32, 0xE9, 0x00, 0x00, 0x00, 0x00)
AMD64_STUB(LeaRspBelowRedZone, 0x48, 0x8D, 0xA4, 0x24, 0x70, 0xFF, 0xFF, 0xFF)
AMD64_STUB(LeaRspRestore, 0x48, 0x8D, 0xA4, 0x24, 0x90, 0x00, 0x00, 0x00)
AMD64_STUB(Nop1, 0x90)
AMD64_STUB(Nop2, 0x66, 0x90)
AMD64_STUB(Nop3, 0x0F, 0x1F, 0x00)
AMD64_STUB(Nop4, 0x0F, 0x1F, 0x40, 0x00)
AMD64_STUB(Nop5, 0x0F, 0x1F, 0x44, 0x00, 0x00)
AMD64_STUB(Nop6, 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00)
AMD64_STUB(Nop7, 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00)
#undef AMD64_STUB
inline constexpr Entry kNops[] = {kNop1, kNop2, kNop3, kNop4, kNop5, kNop6, kNop7};
inline constexpr Entry kMonitorx = {"MONITORX", nullptr, 0};
inline constexpr Entry kMwaitx = {"MWAITX", nullptr, 0};
inline constexpr Entry kClzero = {"CLZERO", nullptr, 0};
inline constexpr Entry kRdpru = {"RDPRU", nullptr, 0};
inline constexpr Entry kMcommit = {"MCOMMIT", nullptr, 0};
inline constexpr Entry kExtrq = {"EXTRQ", nullptr, 0};
inline constexpr Entry kInsertq = {"INSERTQ", nullptr, 0};
inline constexpr Entry kExtrqRegisterForm = {"EXTRQ register form", nullptr, 0};
inline constexpr Entry kInsertqRegisterForm = {"INSERTQ register form", nullptr, 0};
inline constexpr Entry kMovntss = {"MOVNTSS", nullptr, 0};
inline constexpr Entry kMovntsd = {"MOVNTSD", nullptr, 0};
inline constexpr std::uint8_t kMovsStoreOpcode = 0x11;
inline constexpr std::uint8_t kPshufbZero = 0x80;
inline constexpr std::uint8_t kTrapFill = 0xCC;
inline constexpr std::size_t kStubAlignment = 16;
inline constexpr std::size_t kRedZoneSpillFrame = 0x90;
} // namespace Amd64OnlySubstitutionTable
} // namespace Codegen
@@ -8,10 +8,19 @@
namespace Codegen {
enum class Amd64OnlyLowering : std::uint8_t {
InPlace,
Trampoline,
Unsupported
};
struct Amd64OnlyMatch {
std::string InstructionName;
std::size_t Length;
Amd64OnlyLowering Lowering;
std::vector<std::uint8_t> ReplacementBytes;
std::vector<std::uint8_t> StubBody;
std::size_t ReturnBranchOffset;
};
struct Amd64OnlySubstitutionReport {
@@ -19,6 +28,7 @@ struct Amd64OnlySubstitutionReport {
Domain::FileByteOffset Offset;
std::size_t OriginalLength;
std::size_t ReplacementLength;
Amd64OnlyLowering Lowering;
};
}
@@ -10,6 +10,7 @@ struct DecodedInstruction {
const std::uint8_t* Data;
std::size_t Length;
[[nodiscard]] std::size_t OpcodeOffset() const;
[[nodiscard]] bool IsShaNi() const;
[[nodiscard]] bool IsExtrq() const;
[[nodiscard]] bool IsInsertq() const;
@@ -0,0 +1,25 @@
#ifndef CODEGEN_X86_SSE4ALOWERING_HPP
#define CODEGEN_X86_SSE4ALOWERING_HPP
#include <codegen/x86/Sse4aOperands.hpp>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <vector>
namespace Codegen {
struct LoweredBody {
std::vector<std::uint8_t> Bytes;
std::size_t ReturnBranchOffset;
};
class Sse4aLowering {
public:
[[nodiscard]] std::optional<std::vector<std::uint8_t>> LowerInPlace(const Sse4aOperands& operands, std::size_t originalLength) const;
[[nodiscard]] LoweredBody LowerOutOfLine(const Sse4aOperands& operands) const;
};
}
#endif
@@ -0,0 +1,22 @@
#ifndef CODEGEN_X86_SSE4AOPERANDS_HPP
#define CODEGEN_X86_SSE4AOPERANDS_HPP
#include <cstddef>
#include <cstdint>
namespace Codegen {
struct Sse4aOperands {
bool Insertq;
bool RegisterForm;
std::uint8_t Destination;
std::uint8_t Source;
std::uint8_t Length;
std::uint8_t Index;
};
[[nodiscard]] Sse4aOperands DecodeSse4a(const std::uint8_t* data, std::size_t length);
}
#endif
@@ -106,6 +106,10 @@ inline constexpr std::uint8_t OneByteLoop = 0xE0;
inline constexpr std::uint8_t OneByteLoopMax = 0xE2;
inline constexpr std::uint8_t OneByteInOutImm8Min = 0xE4;
inline constexpr std::uint8_t OneByteInOutImm8Max = 0xE7;
inline constexpr std::uint8_t OneByteInt = 0xCD;
inline constexpr std::uint8_t OneByteRetImm16 = 0xC2;
inline constexpr std::uint8_t OneByteRetFarImm16 = 0xCA;
inline constexpr std::uint8_t OneByteEnter = 0xC8;
inline constexpr std::uint8_t TwoByteJccRel32Min = 0x80;
inline constexpr std::uint8_t TwoByteJccRel32Max = 0x8F;
@@ -158,6 +162,19 @@ inline constexpr std::uint8_t TwoByteShufpdImm8 = 0xC6;
inline constexpr std::uint8_t TwoByteGrp9 = 0xC7;
inline constexpr std::uint8_t TwoByteExtrqInsertqImm8Imm8 = 0x78;
inline constexpr std::uint8_t TwoByteExtrqInsertqModRm = 0x79;
inline constexpr std::uint8_t TwoByteMovnts = 0x2B;
inline constexpr std::uint8_t TwoByteGrp6 = 0x00;
inline constexpr std::uint8_t TwoByteLar = 0x02;
inline constexpr std::uint8_t TwoByteLsl = 0x03;
inline constexpr std::uint8_t TwoBytePrefetchw = 0x0D;
inline constexpr std::uint8_t TwoByteMovCrDrMin = 0x20;
inline constexpr std::uint8_t TwoByteMovCrDrMax = 0x23;
inline constexpr std::uint8_t TwoBytePopcnt = 0xB8;
inline constexpr std::uint8_t TwoByteUd1 = 0xB9;
inline constexpr std::uint8_t TwoByteBtc = 0xBB;
inline constexpr std::uint8_t TwoByteXaddRm8 = 0xC0;
inline constexpr std::uint8_t TwoByteMovnti = 0xC3;
inline constexpr std::uint8_t TwoBytePinsrw = 0xC4;
inline constexpr std::uint8_t VexNoModRmMin = 0x77;
inline constexpr std::uint8_t VexNoModRmMax = 0x77;
@@ -171,6 +188,7 @@ inline constexpr std::uint8_t EvexMap0F38 = 0x02;
inline constexpr std::size_t ImmSizeNone = 0;
inline constexpr std::size_t ImmSize8 = 1;
inline constexpr std::size_t ImmSize16 = 2;
inline constexpr std::size_t ImmSizeEnter = 3;
inline constexpr std::size_t ImmSize32 = 4;
inline constexpr std::size_t ImmSize64 = 8;