M68K Assembler
Use this to glue Motorola 68K CPU instructions together at runtime and caveman your way to success.
Only supports a few basic operations as it's geared towards move heavy rendering code.
Will likely only work properly/safely for the M68000 or M68010, so Neo Geo, Megadrive, Early Macs. JAGUAR.
Getting it to compile and work for your needs is an exercise left to the reader.
Sample program;
Add 2 numbers together and store the result;
const int8_t A = 12;
const int8_t B = 30;
int16_t result = 0;
uint16_t code[8]; // program storage is uint16_t
M68KBuilder program(code, sizeof(code)); // code, sizeof(code)
program.moveq(asm_data_reg_t::d0, A); // MOVEQ #A,D0 + 1
program.moveq(asm_data_reg_t::d1, B); // MOVEQ #B,D1 + 1
program.add_w(asm_data_reg_t::d1, asm_data_reg_t::d0); // ADD.W D1,D0 + 1
program.move_addr_l(&result, asm_addr_reg_t::a0); // MOVEA.L #result,A0 + 3
program.move_w(asm_dn(asm_data_reg_t::d0), asm_ind_an(asm_addr_reg_t::a0)); // MOVE.W D0,(A0) + 1
program.rts(); // RTS + 1, program size = 8 words
program.finish();
program.execute();
Assembler.h
#pragma once
// #include
#include "types.h"
/*
witchEngine code generator
config settings, set with a #define;
M68K_Validate TRUE enables safety tests
FALSE builds faster but skips validation
testing;
M68K_Validate = TRUE, run .finish(); then .execute();
feeling dangerous;
run .execute(); if program is known to be sound.
known issues:
malformed non-returning programs may pass .finish() and run if final word in program is 0x4E75 (the checker is DUMB and very minimal)
The ->execute(); function is pretty heavyweight and backs up registers D2->D7, A2->A6
Other registers; D0,D1, A0,A1,A7 are clobbered but managed by GCC
switch codeSpace public and make your own if that's a problem.
rejects (when validation on):
stupid programs (aspirational goal)
too large programs
programs that don't return and can't be patched to work
Resources used and worth reading;
https://www.nxp.com/docs/en/reference-manual/MC68000UM.pdf
https://www.thedigitalcatonline.com/blog/2019/03/04/motorola-68000-addressing-modes/
https://mrjester.hapisan.com/04_MC68/Index.html
*/
#ifndef M68K_Validate
#define M68K_Validate true
#endif
#define FORCE_INLINE [[gnu::always_inline]]
// Registers
enum class asm_data_reg_t : uint16_t
{
d0 = 0x00, d1 = 0x01, d2 = 0x02, d3 = 0x03, d4 = 0x04, d5 = 0x05, d6 = 0x06, d7 = 0x07,
};
enum class asm_addr_reg_t : uint16_t
{
a0 = 0x00, a1 = 0x01, a2 = 0x02, a3 = 0x03, a4 = 0x04, a5 = 0x05, a6 = 0x06, a7 = 0x07,
};
// Register combined with an effective address flag
enum class asm_ea_t : uint16_t {};
// Register modifiers
// e.g. to make a (a0)+
// asm_ea_t a0plus = asm_an_plus(asm_addr_reg_t::a0)
constexpr inline asm_ea_t asm_dn(const asm_data_reg_t reg) { return static_cast(static_cast(reg)); } // Dn
constexpr inline asm_ea_t asm_an(const asm_addr_reg_t reg) { return static_cast(0x08 | (static_cast(reg))); } // An
constexpr inline asm_ea_t asm_ind_an(const asm_addr_reg_t reg) { return static_cast(0x10 | (static_cast(reg))); } // (An)
constexpr inline asm_ea_t asm_an_plus(const asm_addr_reg_t reg) { return static_cast(0x18 | (static_cast(reg))); } // (An)+
constexpr inline asm_ea_t asm_sub_an(const asm_addr_reg_t reg) { return static_cast(0x20 | (static_cast(reg))); } // -(An)
constexpr inline asm_ea_t asm_d16_an(const asm_addr_reg_t reg) { return static_cast(0x28 | (static_cast(reg))); } // d16(An), + extension
// asm consts
const uint16_t asm_nop = 0x4E71;
const uint16_t asm_rts = 0x4E75;
const uint16_t asm_move_b = 0x1000;
const uint16_t asm_move_l = 0x2000;
const uint16_t asm_move_w = 0x3000;
// Helpers
constexpr bool registerOK(const asm_data_reg_t reg)
{
return static_cast(reg) < 8;
}
constexpr bool registerOK(const asm_addr_reg_t reg)
{
return static_cast(reg) < 8;
}
class M68KBuilder
{
public:
#if M68K_Validate
M68KBuilder(uint16_t* storage)
{
buildState = buildState_t::EMPTY;
codeCapacity = asm_rts; // reuse storage
codeSpace = &codeCapacity;
}
M68KBuilder(uint16_t* storage, uint16_t sizeBytes) : codeSpace(storage), codeCapacity(sizeBytes / 2)
{
if((storage == nullptr) || (codeCapacity == 0))
{
buildState = buildState_t::EMPTY;
codeCapacity = asm_rts;
codeSpace = &codeCapacity;
}
};
#else
M68KBuilder(uint16_t* storage) : codeSpace(storage), write(storage) {}
M68KBuilder(uint16_t* storage, uint16_t) : codeSpace(storage), write(storage) {}
#endif
// validate output
FORCE_INLINE void finish()
{
#if M68K_Validate
// Already closed in constuctor
// if overwritten to asm_rts by a fail(), fall into buildState error as insert == 0
if(codeCapacity < 1)
return;
// empty or failed doesn't break things
if((insert == 0) || (buildState == buildState_t::ERROR))
{
// alert("bad or empty program generated");
fail();
return;
}
// ensure code returns
if(codeSpace[insert - 1] != asm_rts)
{
emitCode(asm_rts);
}
// incase above broke everything
if(buildState == buildState_t::ERROR)
{
fail();
}
#endif
}
FORCE_INLINE void execute()
{
asm volatile(
"movem.l %%d2-%%d7/%%a2-%%a6, -(%%sp)\n\t" /* save registers D2->D7, A2->A6 */
"move.l %0, %%a0\n\t" /* codeSpace */
"jsr (%%a0)\n\t" /* codeSpace->(); */
"movem.l (%%sp)+, %%d2-%%d7/%%a2-%%a6" /* restore registers */
:
: "r"(codeSpace)
: "d0", "d1", "a0", "a1", "cc", "memory" /* GCC handles d0,d1,a0,a1*/
);
}
void fail()
{
#if M68K_Validate
codeCapacity = asm_rts; // write return instruction
*codeSpace = codeCapacity; // point at it
buildState = buildState_t::ERROR; // disable more writes
insert = 0; // force == 0 empty fail
#else
*codeSpace = asm_rts;
write = codeSpace + 1;
#endif
}
/*
functions
*/
// 2 bytes
FORCE_INLINE void rts()
{
emitCode(asm_rts);
}
FORCE_INLINE void nop()
{
emitCode(asm_nop);
}
// 6 bytes
FORCE_INLINE void move_addr_l(uint16_t* source, asm_addr_reg_t destRegister)
{
emitCode(asm_move_l | ((static_cast(destRegister) & 0x07) << 9) | 0x0040 | 0x003C);
emitData(static_cast((reinterpret_cast(source) >> 16) & 0xFFFF));
emitData(static_cast(reinterpret_cast(source) & 0xFFFF));
};
// 6 bytes
FORCE_INLINE void move_addr_w(uint16_t* source, asm_addr_reg_t destRegister)
{
emitCode(asm_move_w | ((static_cast(destRegister) & 0x07) << 9) | 0x0040 | 0x003C);
emitData(static_cast((reinterpret_cast(source) >> 16) & 0xFFFF));
emitData(static_cast(reinterpret_cast(source) & 0xFFFF));
};
// move byte source ea -> destination ea [ optional ea ]
FORCE_INLINE void move_b(asm_ea_t source, asm_ea_t dest, int16_t sourceDisplacement = 0, int16_t destDisplacement = 0)
{
const uint16_t sourceBits = static_cast(source);
const uint16_t destBits = static_cast(dest);
emitCode(asm_move_b | ((destBits & 0x07) << 9) | ((destBits & 0x38) << 3) | sourceBits);
if((sourceBits & 0x38) == 0x28)
emitData(static_cast(sourceDisplacement));
if((destBits & 0x38) == 0x28)
emitData(static_cast(destDisplacement));
}
// move word source ea -> destination ea [ optional ea ]
FORCE_INLINE void move_w(asm_ea_t source, asm_ea_t dest, int16_t sourceDisplacement = 0, int16_t destDisplacement = 0)
{
const uint16_t sourceBits = static_cast(source);
const uint16_t destBits = static_cast(dest);
emitCode(asm_move_w | ((destBits & 0x07) << 9) | ((destBits & 0x38) << 3) | sourceBits);
if((sourceBits & 0x38) == 0x28)
emitData(static_cast(sourceDisplacement));
if((destBits & 0x38) == 0x28)
emitData(static_cast(destDisplacement));
}
// move long source ea -> destination ea [ optional ea ]
FORCE_INLINE void move_l(asm_ea_t source, asm_ea_t dest, int16_t sourceDisplacement = 0, int16_t destDisplacement = 0)
{
const uint16_t sourceBits = static_cast(source);
const uint16_t destBits = static_cast(dest);
emitCode(asm_move_l | ((destBits & 0x07) << 9) | ((destBits & 0x38) << 3) | sourceBits);
if((sourceBits & 0x38) == 0x28)
emitData(static_cast(sourceDisplacement));
if((destBits & 0x38) == 0x28)
emitData(static_cast(destDisplacement));
}
// 2 bytes move
FORCE_INLINE void moveq(asm_data_reg_t destRegister, int8_t value)
{
emitCode(0x7000 | ((static_cast(destRegister) & 0x07) << 9) | static_cast(value));
}
// 2 bytes, data register -> data register
FORCE_INLINE void add_w(asm_data_reg_t sourceRegister, asm_data_reg_t destRegister)
{
emitCode(0xD040 | ((static_cast(destRegister) & 0x07) << 9) | (static_cast(sourceRegister) & 0x07));
}
FORCE_INLINE void or_b(asm_data_reg_t sourceRegister, asm_data_reg_t destRegister)
{
emitCode(0x8000 | ((static_cast(destRegister) & 0x07) << 9) | (static_cast(sourceRegister) & 0x07));
}
// 4 bytes + optional displacement
FORCE_INLINE void andi_b(uint8_t value, asm_ea_t dest, int16_t displacement = 0)
{
const uint16_t destBits = static_cast(dest);
emitCode(0x0200 | destBits);
emitData(value);
if((destBits & 0x38) == 0x28)
emitData(static_cast(displacement));
}
// 2 bytes + optional displacement
FORCE_INLINE void clr_b(asm_ea_t dest, int16_t displacement = 0)
{
const uint16_t destBits = static_cast(dest);
emitCode(0x4200 | destBits);
if((destBits & 0x38) == 0x28)
emitData(static_cast(displacement));
}
// 2 bytes + optional displacement
FORCE_INLINE void clr_l(asm_ea_t dest, int16_t displacement = 0)
{
const uint16_t destBits = static_cast(dest);
emitCode(0x4280 | destBits);
if((destBits & 0x38) == 0x28)
emitData(static_cast(displacement));
}
private:
uint16_t* codeSpace;
#if M68K_Validate
uint16_t codeCapacity; // max size
uint16_t insert = 0; // write
enum buildState_t : uint8_t
{
OK, // Can execute
FULL, // No more
ERROR, // bad
EMPTY, // no code!
} buildState = buildState_t::OK;
#else
uint16_t* write;
#endif
FORCE_INLINE constexpr void emitCode(uint16_t code)
{
emitData(code);
#if M68K_Validate
// check code can return
if(buildState == buildState_t::FULL)
{
if(code != asm_rts)
{
fail();
}
}
#endif
}
FORCE_INLINE constexpr void emitData(uint16_t data)
{
#if M68K_Validate
if(buildState == buildState_t::OK)
{
codeSpace[insert] = data;
++insert;
if(insert == codeCapacity)
{
buildState = buildState_t::FULL;
}
}
else if(buildState == buildState_t::FULL)
{
// alert("program already full - fail");
fail();
}
#else
*write++ = data;
#endif
}
};