Skip to content

Instantly share code, notes, and snippets.

@AMalahov
Last active July 4, 2023 21:25
Show Gist options
  • Save AMalahov/7c8df89e2dfee956fd2577073981a22d to your computer and use it in GitHub Desktop.
Save AMalahov/7c8df89e2dfee956fd2577073981a22d to your computer and use it in GitHub Desktop.
#include <iostream>
#include <cstdio>
#include <cassert>
//
// init
//
const int STACK_SIZE = 1000;
thread_local int stack[STACK_SIZE];
int BP;
int SP;
int AX, BX;
//
// utility
//
void printRegisters(char* msg) {
std::cout << msg << ": " << "BP=" << BP << ", SP=" << SP << ", AX=" << AX << std::endl;
}
//
// "system" functions
//
void threadSetup() {
//stack = new int[STACK_SIZE];
BP = SP = STACK_SIZE; // these will index beyond stack's end, but that's ok, because we decrement before first use
}
void threadTearDown() {
//delete[] stack;
}
void push(int value) {
--SP;
stack[SP] = value;
}
void pop(int& result) {
result = stack[SP];
++SP;
}
//
// actual code
//
int triple(int a) {
int result = a * 3;
return result;
}
int triple_noLocals(int a) {
SP -= 1; // move pointer to unused location, where we can store what we need
stack[SP] = a * 3;
return stack[SP];
}
int triple_noL_noParams() { // `a` is at index 999, SP == 999
SP -= 1; // SP == 998, stack[SP + 1] == a
stack[SP] = stack[SP + 1] * 3;
return stack[SP];
}
void triple_noL_noP_noReturn() { // `a` at 998, SP == 998
SP -= 1; // SP == 997
stack[SP] = stack[SP + 1] * 3;
AX = stack[SP];
SP += 1; // finally we can cleanup locals right in the function body, SP == 998
}
void triple_noLPR_withAnchor() {
push(BP);
BP = SP;
SP -= 1;
stack[BP - 1] = stack[BP + 1] * 3;
AX = stack[BP - 1];
SP = BP;
pop(BP);
}
int myAlgo(int a, int b) {
int t1 = a * 3;
int t2 = b * 3;
return t1 - t2;
}
void myAlgo_noLPR() { // `a` is at index 997, `b` at 998, SP == 997
SP -= 2; // SP == 995
stack[SP + 1] = stack[SP + 2] * 3;
stack[SP] = stack[SP + 3] * 3;
AX = stack[SP + 1] - stack[SP];
SP += 2; // cleanup locals, SP == 997
}
void myAlgo_noLPR_withAnchor() { // `a` at 997, `b` at 998, SP == 997
push(BP); // SP == 996
BP = SP; // create anchor, stack[BP] == old value of BP, now BP == 996
SP -= 2; // SP == 994
stack[BP - 1] = stack[BP + 1] * 3;
stack[BP - 2] = stack[BP + 2] * 3;
AX = stack[BP - 1] - stack[BP - 2];
SP = BP; // cleanup locals, SP == 996
pop(BP); // SP == 997
}
int myAlgo_withCalls(int a, int b) {
int t1 = triple(a);
int t2 = triple(b);
return t1 - t2;
}
// pushes the address of the code at label's location on the stack
// NOTE: this gonna work only with 32-bit compiler (so that pointer is 32-bit and fits in int)
#define PUSH_ADDRESS(labelName) { \
void* tmpPointer; \
__asm{ mov [tmpPointer], offset labelName } \
push(reinterpret_cast<int>(tmpPointer)); \
}
// why we need indirection, read http://stackoverflow.com/a/13301627/264047
#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
// generates token (not a string) we will use as label name.
// Example: LABEL_NAME(155) will generate token `lbl_155`
#define LABEL_NAME(num) TOKENPASTE2(lbl_, num)
#define CALL_IMPL(funcLabelName, callId) \
PUSH_ADDRESS(LABEL_NAME(callId)); \
goto funcLabelName; \
LABEL_NAME(callId) :
// saves return address on the stack and jumps to label `funcLabelName`
#define CALL(funcLabelName) CALL_IMPL(funcLabelName, __LINE__)
// takes address at the top of stack and jump there
#define RET() { \
int tmpInt; \
pop(tmpInt); \
void* tmpPointer = reinterpret_cast<void*>(tmpInt); \
__asm{ jmp tmpPointer } \
}
void myAlgo_asm() {
goto my_algo_start;
triple_label:
push(BP);
BP = SP;
SP -= 1;
// stack[BP] == old BP, stack[BP + 1] == return address
stack[BP - 1] = stack[BP + 2] * 3;
AX = stack[BP - 1];
SP = BP;
pop(BP);
RET();
my_algo_start:
push(BP); // SP == 995
BP = SP; // BP == 995; stack[BP] == old BP, stack[BP + 1] == dummy return address, `a` at [BP + 2], `b` at [BP + 3]
SP -= 2; // SP == 993
push(AX);
push(stack[BP + 2]);
CALL(triple_label);
stack[BP - 1] = AX;
SP -= 1;
pop(AX);
push(AX);
push(stack[BP + 3]);
CALL(triple_label);
stack[BP - 2] = AX;
SP -= 1;
pop(AX);
AX = stack[BP - 1] - stack[BP - 2];
SP = BP; // cleanup locals, SP == 997
pop(BP);
}
int main() {
threadSetup();
int original = triple(11);
std::cout << "triple original: "<< original << std::endl;
assert(original == triple_noLocals(11));
// now SP == 999, but we don't need the value at stack[999] anymore
// and we will move the stack index back, so we can reuse it later
SP += 1; // SP == 1000
printRegisters("no locals");
push(11); // SP == 999
assert(original == triple_noL_noParams());
SP += 2; // cleanup 1 local and 1 parameter
printRegisters("no l, no p");
push(AX); // SP == 999
push(11); // SP == 998
triple_noL_noP_noReturn();
assert(original == AX);
printRegisters("no LPR, before cleanup");
SP += 1; // cleanup param
// locals were cleaned up in the function body, so we don't need to do it here
pop(AX); // restore AX
printRegisters("no LPR, after cleanup");
push(AX);
push(11);
triple_noLPR_withAnchor();
assert(original == AX);
printRegisters("no LPR w/ A, before cleanup");
SP += 1;
pop(AX);
printRegisters("no LPR w/ A, after cleanup");
original = myAlgo(11, 22);
std::cout << std::endl << "myAlgo original: " << original << std::endl;
push(AX); // SP == 999
push(22); // SP == 998
push(11); // SP == 997
myAlgo_noLPR();
assert(original == AX);
printRegisters("no LPR, before cleanup");
SP += 2;
pop(AX);
printRegisters("no LPR, after cleanup");
push(AX);
push(22);
push(11);
myAlgo_noLPR_withAnchor();
assert(original == AX);
printRegisters("no LPR w/ A, before cleanup");
SP += 2;
pop(AX);
printRegisters("no LPR w/ A, after cleanup");
assert(original == myAlgo_withCalls(11, 22));
push(AX);
push(22);
push(11);
push(7777); // dummy value, so that offsets inside function are like we've pushed return address
myAlgo_asm();
assert(original == AX);
printRegisters("asm, before cleanup");
SP += 1; // pop dummy "return address"
SP += 2;
pop(AX);
printRegisters("asm, after cleanup");
getchar();
threadTearDown();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment