ReC98/codegen.hpp

211 lines
6.5 KiB
C++

/// Custom code generation for pseudoregisters
/// ------------------------------------------
/// Since __emit__() always inlines, we can use it implement our own code
/// generation to bypass compiler bugs or quirks related to pseudoregisters, by
/// directly outputting the intended machine code:
///
/// • Step 1: Define opcodes and R/M bytes for all needed instructions and
/// registers
/// • Step 2: Implement needed operations as inlined functions via __emit__(),
/// taking all required instruction components as parameters
/// • Step 3: Add macros that token-paste pseudoregisters onto the prefixes of
/// the opcode byte constants. This way, we hide the pseudoregisters
/// from the compiler, and the constants from usage code.
///
/// Provides workarounds for the following issues:
///
/// 1) Turbo C++ 4.0 generates wrong segment prefix opcodes for the _FS and _GS
/// pseudoregisters - 0x46 (INC SI) and 0x4E (DEC SI) rather than the correct
/// 0x64 and 0x65, respectively. These prefixes are also not supported in
/// inline assembly, which is limited to pre-386 anyway. Compiling via
/// assembly (`#pragma inline`) would work and generate the correct
/// instructions, but that would incur yet another dependency on a 16-bit
/// TASM for something honestly quite insignificant.
/// 2) If _SI or _DI are used within a function, Turbo C++ 4.0 always generates
/// a `PUSH SI` and `PUSH DI` instruction in the function prolog, and a
/// `POP DI` and `POP SI` instruction in the epilog, in this order. If a
/// function needs both these registers and a different prolog or epilog, all
/// use of these registers must be hidden via __emit__(). These macros can
/// help retain some readability in this case.
///
/// Provides access to the following instructions that are unavailable in Turbo
/// C++ 4.0's inline assembler, for arbitrary registers:
///
/// • IMUL r16, r/m16, imm8
#if defined(__TURBOC__) && defined(__MSDOS__)
// Declared in <dos.h> in these compilers.
void __emit__(uint8_t __byte, ...);
#endif
struct X86 {
enum Prefix {
P_DS = 0x3E,
P_SS = 0x36,
P_ES = 0x26,
P_FS = 0x64,
P_GS = 0x65,
P_OPERAND_SIZE = 0x66,
};
enum Reg8 {
R_CL = 1,
};
enum Reg16 {
R_AX = 0,
R_DX = 2,
R_BX = 3,
R_SI = 6,
R_DI = 7,
};
enum Reg32 {
R_EAX = 0,
};
enum RM {
RM_ADDRESS_DI = 0x05,
RM_ADDRESS_BP = 0x06,
};
enum OpRegMem {
OR_RM_R_32 = 0x09, // OR r/m32, r32
MOV_RM_R_16 = 0x89, // MOV r/m16, r16
MOV_RM_R_32 = 0x89, // MOV r/m32, r32
MOV_R_RM_8 = 0x8A, // MOV r8, r/m8
MOV_R_RM_16 = 0x8B, // MOV r16, r/m16
MOV_R_RM_32 = 0x8B, // MOV r32, r/m32
LEA_R_M_16 = 0x8D, // LEA r16, m
};
enum OpRegRegMem {
CMP_RM_R_16 = 0x3B, // CMP r/m16, r16
IMUL_R_RM_IMM_8 = 0x6B, // IMUL r16, r/m16, imm8
};
// Emitters
// --------
static void reg_reg(OpRegRegMem op, Reg16 dst, Reg16 src, uint8_t imm = 0) {
if(imm) {
__emit__(op, (0xC0 + (dst * 8) + src), imm);
} else {
__emit__(op, (0xC0 + (dst * 8) + src));
}
}
static void reg_mem(
OpRegMem op, Prefix prefix, RM rm, Reg16 reg, uint8_t disp = 0
) {
if(!(
((prefix == P_SS) && (rm == RM_ADDRESS_BP)) ||
((prefix == P_DS) && (rm != RM_ADDRESS_BP))
)) {
__emit__(prefix);
}
if(disp || (rm == RM_ADDRESS_BP)) {
__emit__(op, (0x40 + ((reg * 8) + rm)), disp);
} else {
__emit__(op, ((reg * 8) + rm));
}
}
static void reg_mem(
OpRegMem op, Prefix prefix, RM rm, Reg32 reg, uint8_t disp = 0
) {
__emit__(P_OPERAND_SIZE);
reg_mem(op, prefix, rm, static_cast<Reg16>(reg), disp);
}
static void mov_to_reg(Prefix prefix, RM rm, Reg8 reg, uint8_t disp = 0) {
reg_mem(MOV_R_RM_8, prefix, rm, static_cast<Reg16>(reg), disp);
}
static void mov_to_reg(Prefix prefix, RM rm, Reg16 reg, uint8_t disp = 0) {
reg_mem(MOV_R_RM_16, prefix, rm, reg, disp);
}
static void mov_to_reg(Prefix prefix, RM rm, Reg32 reg, uint8_t disp = 0) {
reg_mem(MOV_R_RM_32, prefix, rm, reg, disp);
}
static void mov_to_mem(Prefix prefix, RM rm, Reg16 reg, uint8_t disp = 0) {
reg_mem(MOV_RM_R_16, prefix, rm, reg, disp);
}
static void mov_to_mem(Prefix prefix, RM rm, Reg32 reg, uint8_t disp = 0) {
reg_mem(MOV_RM_R_32, prefix, rm, reg, disp);
}
// --------
};
// First macro layer to transform pseudoregisters into x86 constants
// -----------------------------------------------------------------
#define _stack_to_reg(op, dst_reg, imm) \
X86::reg_mem(op, X86::P_SS, X86::RM_ADDRESS_BP, X86::R##dst_reg, imm);
#define _cmp_reg_reg(dst_reg, src_reg) \
X86::reg_reg(X86::CMP_RM_R_16, X86::R##dst_reg, X86::R##src_reg);
#define _imul_reg_to_reg(dst_reg, src_reg, imm) \
X86::reg_reg(X86::IMUL_R_RM_IMM_8, X86::R##dst_reg, X86::R##src_reg, imm);
#define _lea_local_to_reg(dst_reg, src_top, src_ptr) \
_stack_to_reg(X86::LEA_R_M_16, dst_reg, ( \
reinterpret_cast<uint8_t __ss *>(src_ptr) - \
reinterpret_cast<uint8_t __ss *>(src_top) \
));
#define _mov_param_to_reg(dst_reg, offset) \
_stack_to_reg(X86::MOV_R_RM_16, dst_reg, offset);
#define _mov_to_reg(dst_reg, src_sgm, src_off, src_disp) \
X86::mov_to_reg( \
X86::P##src_sgm, X86::RM_ADDRESS##src_off, X86::R##dst_reg, src_disp \
);
#define _mov_to_mem(dst_sgm, dst_off, dst_disp, src_reg) \
X86::mov_to_mem( \
X86::P##dst_sgm, X86::RM_ADDRESS##dst_off, X86::R##src_reg, dst_disp \
);
// Removing [val] from the parameter lists of the template functions below
// perfects the inlining.
#define poked(sgm, off, val) \
_EAX = val; \
X86::reg_mem( \
X86::MOV_RM_R_32, X86::P##sgm, X86::RM_ADDRESS##off, X86::R_EAX \
);
#define poke_or_d(sgm, off, val) \
_EAX = val; \
X86::reg_mem( \
X86::OR_RM_R_32, X86::P##sgm, X86::RM_ADDRESS##off, X86::R_EAX \
);
// -----------------------------------------------------------------
// Second macro layer to allow pseudoregister renaming
// ---------------------------------------------------
#define cmp_reg_reg(dst_reg, src_reg) \
_cmp_reg_reg(dst_reg, src_reg)
#define imul_reg_to_reg(dst_reg, src_reg, imm) \
_imul_reg_to_reg(dst_reg, src_reg, imm)
#define lea_local_to_reg(dst_reg, src_top, src_ptr) \
_lea_local_to_reg(dst_reg, src_top, src_ptr)
#define mov_param_to_reg(dst_reg, src_ptr) \
_mov_param_to_reg(dst_reg, src_ptr)
#define mov_to_reg(dst_reg, src_sgm, src_off, src_disp) \
_mov_to_reg(dst_reg, src_sgm, src_off, src_disp)
#define mov_to_mem(dst_sgm, dst_off, dst_disp, src_reg) \
_mov_to_mem(dst_sgm, dst_off, dst_disp, src_reg)
// ---------------------------------------------------
/// ----------------------