Skip to content

Commit fd95c42

Browse files
committed
Manage locals and calls using the stack directly
1 parent a926ae8 commit fd95c42

File tree

9 files changed

+141
-65
lines changed

9 files changed

+141
-65
lines changed

TODO.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Roadmap
22

33
- [ ] Garbage collection for heap objects (structs)
4-
- [ ] Remove locals and add registers: src/executer/ByteCode.h:117
54
- [ ] Dynamic Strings
65
- [ ] Arrays
76

@@ -21,4 +20,5 @@
2120
- [ ] FFI for Windows
2221

2322
- [x] Throw when finding a type annotation that does not name a type (struct or primitive)
24-
- [x] Throw when encountering a function that does not return on all paths
23+
- [x] Throw when encountering a function that does not return on all paths
24+
- [x] Remove locals and add registers: src/executer/ByteCode.h:117
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# expect_result=1
2+
let f(x) = {
3+
if(x > 20) {
4+
let z = 1;
5+
ret z;
6+
}
7+
let y = 2;
8+
ret y;
9+
};
10+
ret f(30);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# expect_result=2
2+
let f(x) = {
3+
if(x > 20) {
4+
let z = 1;
5+
ret z;
6+
}
7+
let y = 2;
8+
ret y;
9+
};
10+
ret f(10);

src/emitter/ByteCodeEmitter.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ void ByteCodeEmitter::run() {
2929
for(const auto& param : fn.second->getHead()->getParameters()) {
3030
localNames.push_back(param->getName());
3131
}
32+
num_params = localNames.size(); // Remember parameter count
3233
function_idxs[fn.first] = code().size();
3334
process(fn.second->getBody(), false);
3435
}
@@ -206,7 +207,9 @@ void ByteCodeEmitter::process(const std::shared_ptr<AST::Node>& node, bool hasCo
206207
if (ret->getExpr()) {
207208
process(ret->getExpr(), true);
208209
}
209-
code().push_back(executor::Instruction(executor::Op::RET));
210+
// RET arg1=num_params, arg2=num_locals
211+
size_t num_locals = localNames.size() - num_params;
212+
code().push_back(executor::Instruction(executor::Op::RET, num_params, num_locals));
210213
break;
211214
}
212215
case AST::NodeType::Assign: {

src/emitter/ByteCodeEmitter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ByteCodeEmitter {
2727
};
2828
std::vector<Backpatch> backpatches;
2929
std::vector<std::string> localNames; // Id is idx
30+
size_t num_params; // Number of parameters for current function
3031

3132
public:
3233
ByteCodeEmitter(const std::map<std::string, std::shared_ptr<AST::Function>> &functions);

src/executer/ByteCode.cpp

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ std::string instructionsToString(const std::vector<Instruction>& instructions, b
2020
{ Op::LOCALS, { "LOCALS", { "ID" } } },
2121
{ Op::LOCALL, { "LOCALL", { "ID" } } },
2222
{ Op::CALL, { "CALL", { "NUM_ARGS" } } },
23-
{ Op::RET, { "RET", { } } },
23+
{ Op::RET, { "RET", { "NUM_PARAMS", "NUM_LOCALS" } } },
2424
{ Op::PUSH, { "PUSH", { "VALUE" } } },
2525
{ Op::POP, { "POP", {} } },
2626
{ Op::ADD, { "ADD", {} } },
@@ -108,47 +108,91 @@ ProgramState ByteCodeVM::run(size_t maxInstructions) {
108108
break;
109109
}
110110
case Op::CALL: {
111-
auto return_address = idx;
111+
word_t num_params = inst.arg1;
112+
word_t jmp_dest = stack.pop();
112113

113-
auto dest = stack.pop();
114-
idx = dest; // Jump to function address
115-
116-
// Bring parameters to locals
117-
std::vector<word_t> locals;
118-
for (int i = 0; i < inst.arg1; ++i) {
119-
locals.push_back(stack.pop());
114+
// Save parameters temporarily (they're on stack in order: param0, param1, ...)
115+
std::vector<word_t> params;
116+
for (word_t i = 0; i < num_params; ++i) {
117+
params.push_back(stack.pop());
120118
}
121119

122-
callstack.push_back({locals, return_address});
120+
stack.push(idx); // Push return address
121+
stack.push(function_stack_base); // Push prev function_stack_base
122+
function_stack_base = stack.size(); // Update function_stack_base to current top
123+
124+
// Push parameters in reverse order (so param0 is at function_stack_base+0)
125+
for (auto it = params.rbegin(); it != params.rend(); ++it) {
126+
stack.push(*it);
127+
}
128+
129+
idx = jmp_dest; // Jump to function
123130
break;
124131
}
125132
case Op::RET: {
126-
if(callstack.empty()){
133+
word_t num_params = inst.arg1;
134+
word_t num_locals = inst.arg2;
135+
136+
// function_stack_base
137+
// |
138+
// Stack layout: [ret_addr][prev_function_stack_base]^[params...][locals...][return_value?]
139+
140+
word_t expected_stack_size = function_stack_base + num_params + num_locals;
141+
bool has_return_value = false;
142+
if (stack.size() > expected_stack_size) {
143+
return_value = stack.pop();
144+
has_return_value = true;
145+
} else {
146+
return_value = 0;
147+
}
148+
149+
// Pop locals
150+
while (stack.size() > function_stack_base + num_params) {
151+
stack.pop();
152+
}
153+
154+
// Pop parameters
155+
for (word_t i = 0; i < num_params; ++i) {
156+
stack.pop();
157+
}
158+
159+
// Check if this is the main function return
160+
if (function_stack_base == 0) {
127161
return ProgramState::Finished;
128162
}
129-
idx = callstack.back().return_address;
130-
callstack.pop_back();
163+
164+
function_stack_base = stack.pop(); // Restore function_stack_base
165+
idx = stack.pop(); // Jump back to return address
166+
167+
if (has_return_value) {
168+
stack.push(return_value);
169+
}
170+
131171
break;
132172
}
133173
case Op::LOCALS: {
134-
// LOCALS ID
135-
auto& locals = callstack.back().locals;
174+
// LOCALS n: Store stack top into local variable/parameter n
175+
word_t value = stack.pop();
136176

137-
auto idx = inst.arg1 + 1;
138-
if(locals.size() <= idx) {
139-
locals.resize(idx); // TODO: Expensive!
177+
word_t localIndex = function_stack_base + inst.arg1;
178+
179+
// Expand stack if necessary
180+
while (stack.size() <= localIndex) {
181+
stack.push(0);
140182
}
141183

142-
auto value = stack.pop();
143-
locals[inst.arg1] = value; // Store value in local variable
184+
stack.set(localIndex, value);
144185
break;
145186
}
146187
case Op::LOCALL: {
147-
// LOCALL ID
148-
ASSURE(!callstack.empty(), "ByteCodeVM: Empty callstack");
149-
auto& locals = callstack.back().locals;
150-
ASSURE(inst.arg1 < locals.size(), "ByteCodeVM: Local variable index out of bounds");
151-
stack.push(locals[inst.arg1]); // Push local variable onto stack
188+
// LOCALL n: Load local variable/parameter n onto stack
189+
word_t localIndex = function_stack_base + inst.arg1;
190+
191+
ASSURE(localIndex < stack.size(),
192+
"ByteCodeVM: Local variable index out of bounds");
193+
194+
word_t value = stack.get(localIndex);
195+
stack.push(value);
152196
break;
153197
}
154198
case Op::PRINTS: {
@@ -325,11 +369,12 @@ ProgramState ByteCodeVM::run(size_t maxInstructions) {
325369
return ProgramState::Paused;
326370
}
327371

328-
ByteCodeVM::ByteCodeVM(const Program& program) :
329-
idx{0ull},
330-
callstack{},
331-
stack{},
332-
program(program),
372+
ByteCodeVM::ByteCodeVM(const Program& program) :
373+
idx{0ull},
374+
function_stack_base{0ull},
375+
return_value{0ull},
376+
stack{},
377+
program(program),
333378
debug{true},
334379
ffiFunctions{},
335380
ffiArgs{} {}

src/executer/ByteCode.h

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -114,41 +114,14 @@ enum class ProgramState {
114114
Finished
115115
};
116116

117-
// TODO: We don't need any of this, we can just use the stack
118-
// https://stackoverflow.com/questions/1395591/what-exactly-is-the-base-pointer-and-stack-pointer-to-what-do-they-point
119-
// https://www.cs.virginia.edu/~evans/cs216/guides/x86.html
120-
121-
// We just need to introduce registers
122-
// esp: Current stack top
123-
// ebp: Stack base pointer, is set to esp in the beginning of a function
124-
// eax: Return value of the function
125-
126-
// On function call:
127-
// - Push the parameters in reverse order
128-
// - Push return address (done by the CALL instruction)
129-
// - Push the current ebp
130-
// - Set ebp to esp
131-
// - Push locals
132-
// Within fucnction:
133-
// - Use ebp + n to access parameters
134-
// - Use ebp + n to access locals
135-
// On function return:
136-
// - Put result into eax register
137-
// - Pop locals from the stack
138-
// - Pop ebp from the stack, put into ebp register
139-
// - Pop return address from the stack, and jump (done by the RET instruction)
140-
// - Pop parameters from the stack (done by the caller, not the callee)
141-
// - Get result from eax register
142-
143-
struct StackFrame {
144-
std::vector<word_t> locals;
145-
word_t return_address;
146-
};
147-
148117
class ByteCodeVM {
149118
private:
150119
word_t idx;
151-
std::vector<StackFrame> callstack;
120+
121+
// Registers to manage stack-based function calls, parameters, locals, and return values
122+
word_t function_stack_base;
123+
word_t return_value;
124+
152125
Stack stack;
153126
std::vector<word_t> heap;
154127
Program program;

src/executer/Stack.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,32 @@ bool Stack::empty() const {
3838
size_t Stack::size() const {
3939
return impl.size();
4040
}
41+
42+
word_t Stack::get(size_t index) const {
43+
if (index >= impl.size()) {
44+
throwConstraintViolated("Stack::get index out of bounds");
45+
}
46+
return impl[index];
47+
}
48+
49+
void Stack::set(size_t index, word_t value) {
50+
if (index >= impl.size()) {
51+
throwConstraintViolated("Stack::set index out of bounds");
52+
}
53+
impl[index] = value;
54+
}
55+
56+
word_t& Stack::operator[](size_t index) {
57+
if (index >= impl.size()) {
58+
throwConstraintViolated("Stack::operator[] index out of bounds");
59+
}
60+
return impl[index];
61+
}
62+
63+
const word_t& Stack::operator[](size_t index) const {
64+
if (index >= impl.size()) {
65+
throwConstraintViolated("Stack::operator[] const index out of bounds");
66+
}
67+
return impl[index];
68+
}
4169
} // namespace executor

src/executer/Stack.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ class Stack {
2222

2323
word_t lookback(size_t n) const;
2424

25+
// Indexed access for register-based VM
26+
word_t get(size_t index) const;
27+
void set(size_t index, word_t value);
28+
word_t& operator[](size_t index);
29+
const word_t& operator[](size_t index) const;
30+
2531
using const_iterator = std::vector<word_t>::const_iterator;
2632

2733
auto begin() const {

0 commit comments

Comments
 (0)