• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rieske / trans / 28460720020

30 Jun 2026 04:42PM UTC coverage: 90.232% (-0.3%) from 90.53%
28460720020

Pull #44

github

rieske
Support pointer members in structs including self-reference

Apply declarator pointer levels when building field types so `int *p`
is not stored as `int`. Use a shared StructBody for struct tags so
`struct Node *next` can refer to an incomplete Node that is completed
when the definition finishes. Tests for int* members and linked nodes.
Pull Request #44: Implement C structs with member access

245 of 306 new or added lines in 12 files covered. (80.07%)

29 existing lines in 3 files now uncovered.

5358 of 5938 relevant lines covered (90.23%)

236113.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.02
/src/codegen/StackMachine.cpp
1
#include "StackMachine.h"
2

3
#include <cassert>
4
#include <algorithm>
5
#include <stdexcept>
6

7
#include "InstructionSet.h"
8

9
namespace {
10
const int MACHINE_WORD_SIZE = 8;
11
const int STACK_ALIGNMENT = 2 * MACHINE_WORD_SIZE;
12
} // namespace
13

14
namespace codegen {
15

16
StackMachine::StackMachine(std::ostream *ostream, std::unique_ptr<InstructionSet> instructionSet, std::unique_ptr<Amd64Registers> registers) :
406 ✔
17
        assembly{ostream},
406 ✔
18
        instructionSet{std::move(instructionSet)},
406 ✔
19
        registers{std::move(registers)} {}
812 ✔
20

21
void StackMachine::generatePreamble(const std::map<std::string, std::string>& constants,
374 ✔
22
        const std::vector<GlobalVariable>& globalVariables) {
23
    assembly.raw(instructionSet->preamble(constants, globalVariables));
374 ✔
24
    for (const auto& global : globalVariables) {
488 ✔
25
        globalHomes.emplace(global.name, Address::globalLabel(global.name, global.sizeInBytes));
114 ✔
26
        // resolve() shell only; home is globalHomes, never register-cached.
27
        globals.emplace(global.name, global.toValue());
114 ✔
28
    }
29
}
374 ✔
30

31
void StackMachine::startProcedure(std::string procedureName, std::vector<Value> values, std::vector<Value> arguments) {
472 ✔
32

33
    emptyGeneralPurposeRegisters();
472 ✔
34
    frameHomes.clear();
472 ✔
35
    assembly.label(instructionSet->label(procedureName));
472 ✔
36
    assembly << instructionSet->push(registers->getBasePointer());
472 ✔
37
    assembly << instructionSet->mov(registers->getStackPointer(), registers->getBasePointer());
472 ✔
38

39
    for (auto& value : values) {
4,982 ✔
40
        scopeValues.insert({value.getName(), value});
4,510 ✔
41
    }
42
    std::size_t integerArgumentRegisterIndex{0};
472 ✔
43
    std::size_t localIndex{scopeValues.size()};
472 ✔
44
    int argumentIndex{0};
472 ✔
45
    for (auto& argument : arguments) {
638 ✔
46
        if (argument.getType() == Type::INTEGRAL && integerArgumentRegisterIndex < registers->getIntegerArgumentRegisters().size()) {
166 ✔
47
            Value registerArgument{argument.getName(), static_cast<int>(localIndex), argument.getType(), argument.getSizeInBytes()};
118 ✔
48
            scopeValues.insert({argument.getName(), registerArgument});
118 ✔
49
            registers->getIntegerArgumentRegisters()[integerArgumentRegisterIndex]->assign(&resolve(argument.getName()));
118 ✔
50
            ++integerArgumentRegisterIndex;
118 ✔
51
            ++localIndex;
118 ✔
52
        } else {
118 ✔
53
            Value stackArgument{argument.getName(), argumentIndex, argument.getType(), argument.getSizeInBytes()};
48 ✔
54
            scopeValues.insert({argument.getName(), stackArgument});
48 ✔
55
            // Stack args live at fixed base-pointer offsets; independent of callee-saved size.
56
            registerFrameHome(argument.getName(), Address::frame(FrameBase::BasePointer,
96 ✔
57
                    (argumentIndex + 2) * MACHINE_WORD_SIZE, argument.getSizeInBytes()));
48 ✔
58
            ++argumentIndex;
48 ✔
59
        }
48 ✔
60
    }
61
    int savedRegistersStack = registers->getCalleeSavedRegisters().size() * MACHINE_WORD_SIZE;
472 ✔
62
    // SP locals use word index; size may span multiple words (structs).
63
    int maxWordEnd = 0;
472 ✔
64
    for (const auto& entry : scopeValues) {
5,148 ✔
65
        if (frameHomes.count(entry.first)) {
4,676 ✔
66
            continue; // stack argument on RBP
48 ✔
67
        }
68
        int words = (entry.second.getSizeInBytes() + MACHINE_WORD_SIZE - 1) / MACHINE_WORD_SIZE;
4,628 ✔
69
        if (words < 1) {
4,628 ✔
NEW
70
            words = 1;
×
71
        }
72
        maxWordEnd = std::max(maxWordEnd, entry.second.getIndex() + words);
4,628 ✔
73
    }
74
    localVariableStackSize = maxWordEnd * MACHINE_WORD_SIZE;
472 ✔
75
    int stackSize = savedRegistersStack + localVariableStackSize;
472 ✔
76
    if (stackSize % STACK_ALIGNMENT) {
472 ✔
77
        assembly << instructionSet->sub(registers->getStackPointer(), localVariableStackSize + MACHINE_WORD_SIZE);
218 ✔
78
    } else {
79
        assembly << instructionSet->sub(registers->getStackPointer(), localVariableStackSize);
254 ✔
80
    }
81

82
    pushCalleeSavedRegisters();
472 ✔
83
    // Stack-pointer locals / reg-arg spill slots include callee-saved space.
84
    for (const auto& entry : scopeValues) {
5,148 ✔
85
        if (frameHomes.count(entry.first)) {
4,676 ✔
86
            continue;
48 ✔
87
        }
88
        registerFrameHome(entry.first, spillSlotAddress(entry.second));
4,628 ✔
89
    }
90
}
472 ✔
91

92
void StackMachine::endProcedure() {
462 ✔
93
    emptyGeneralPurposeRegisters();
462 ✔
94
    scopeValues.clear();
462 ✔
95
    frameHomes.clear();
462 ✔
96
    calleeSavedRegisters.clear();
462 ✔
97
}
462 ✔
98

99
void StackMachine::label(std::string name) {
584 ✔
100
    spillGeneralPurposeRegisters();
584 ✔
101
    assembly.label(instructionSet->label(name));
584 ✔
102
}
584 ✔
103

104
void StackMachine::jump(JumpCondition jumpCondition, std::string label) {
592 ✔
105
    switch (jumpCondition) {
592 ✔
106
    case JumpCondition::IF_EQUAL:
152 ✔
107
        assembly << instructionSet->je(label);
152 ✔
108
        break;
152 ✔
109
    case JumpCondition::IF_NOT_EQUAL:
22 ✔
110
        assembly << instructionSet->jne(label);
22 ✔
111
        break;
22 ✔
112
    case JumpCondition::IF_ABOVE:
36 ✔
113
        assembly << instructionSet->jg(label);
36 ✔
114
        break;
36 ✔
115
    case JumpCondition::IF_BELOW:
56 ✔
116
        assembly << instructionSet->jl(label);
56 ✔
117
        break;
56 ✔
118
    case JumpCondition::IF_ABOVE_OR_EQUAL:
24 ✔
119
        assembly << instructionSet->jge(label);
24 ✔
120
        break;
24 ✔
121
    case JumpCondition::IF_BELOW_OR_EQUAL:
28 ✔
122
        assembly << instructionSet->jle(label);
28 ✔
123
        break;
28 ✔
124
    case JumpCondition::UNCONDITIONAL:
274 ✔
125
    default:
126
        spillGeneralPurposeRegisters();
274 ✔
127
        assembly << instructionSet->jmp(label);
274 ✔
128
    }
129
}
592 ✔
130

131
void StackMachine::spillGeneralPurposeRegisters() {
858 ✔
132
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
12,870 ✔
133
        storeRegisterValue(*reg);
12,012 ✔
134
    }
858 ✔
135
}
858 ✔
136

137
void StackMachine::spillCallerSavedRegisters() {
920 ✔
138
    for (auto& reg : registers->getCallerSavedRegisters()) {
8,280 ✔
139
        storeRegisterValue(*reg);
7,360 ✔
140
    }
920 ✔
141
}
920 ✔
142

143
void StackMachine::emitLoad(Value& symbol, Register& dest) {
2,794 ✔
144
    assembly << instructionSet->mov(memoryOperand(symbol), dest);
2,794 ✔
145
}
2,794 ✔
146

147
void StackMachine::loadWithoutBinding(Value& symbol, Register& dest) {
1,832 ✔
148
    emitLoad(symbol, dest);
1,832 ✔
149
}
1,832 ✔
150

151
void StackMachine::emitStore(Register& source, Value& symbol) {
2,492 ✔
152
    assembly << instructionSet->mov(source, memoryOperand(symbol));
2,492 ✔
153
}
2,492 ✔
154

155
// Bind a freshly computed result to its destination symbol. Global homes are Address-only:
156
// commit to memory; never attach a register to the global Value (loads use scratch only).
157
// Locals/temps use register residence and lazy write-back.
158
void StackMachine::bindResult(Register& reg, Value& result) {
834 ✔
159
    if (addressOf(result).isGlobal()) {
834 ✔
160
        emitStore(reg, result);
24 ✔
161
        assert(result.isStored() && "global Value must not be register-linked");
24 ✔
162
        return;
24 ✔
163
    }
164
    reg.assign(&result);
810 ✔
165
}
166

167
void StackMachine::assignRegisterToSymbol(Register& reg, Value& symbol) {
2,014 ✔
168
    // Always load without binding when the symbol is memory-resident (includes all globals:
169
    // their homes are Address-only; never reg.assign the global Value).
170
    if (residesInMemory(symbol)) {
2,014 ✔
171
        storeRegisterValue(reg);
1,618 ✔
172
        loadWithoutBinding(symbol, reg);
1,618 ✔
173
    } else if (&reg != &symbol.getAssignedRegister()) {
396 ✔
174
        storeRegisterValue(reg);
388 ✔
175
        Register& valueRegister = symbol.getAssignedRegister();
388 ✔
176
        storeRegisterValue(valueRegister);
388 ✔
177
        assembly << instructionSet->mov(valueRegister, reg);
388 ✔
178
    }
179
}
2,014 ✔
180

181
void StackMachine::compare(std::string leftSymbolName, std::string rightSymbolName) {
178 ✔
182
    auto& leftSymbol = resolve(leftSymbolName);
178 ✔
183
    auto& rightSymbol = resolve(rightSymbolName);
178 ✔
184

185
    if (residesInMemory(leftSymbol) && residesInMemory(rightSymbol)) {
178 ✔
186
        Register& rightSymbolRegister = assignRegisterTo(rightSymbol);
122 ✔
187
        assembly << instructionSet->cmp(memoryOperand(leftSymbol), rightSymbolRegister);
122 ✔
188
    } else if (residesInMemory(leftSymbol)) {
56 ✔
189
        assembly << instructionSet->cmp(memoryOperand(leftSymbol), rightSymbol.getAssignedRegister());
16 ✔
190
    } else if (residesInMemory(rightSymbol)) {
40 ✔
191
        assembly << instructionSet->cmp(leftSymbol.getAssignedRegister(), memoryOperand(rightSymbol));
20 ✔
192
    } else {
193
        assembly << instructionSet->cmp(leftSymbol.getAssignedRegister(), rightSymbol.getAssignedRegister());
20 ✔
194
    }
195
}
178 ✔
196

197
void StackMachine::zeroCompare(std::string symbolName) {
140 ✔
198
    auto& symbol = resolve(symbolName);
140 ✔
199
    if (residesInMemory(symbol)) {
140 ✔
200
        assembly << instructionSet->cmp(memoryOperand(symbol), 0);
130 ✔
201
    } else {
202
        assembly << instructionSet->cmp(symbol.getAssignedRegister(), 0);
10 ✔
203
    }
204
}
140 ✔
205

206
void StackMachine::addressOf(std::string operandName, std::string resultName) {
334 ✔
207
    auto& operand = resolve(operandName);
334 ✔
208
    storeInMemory(operand);
334 ✔
209
    Register& resultRegister = get64BitRegister();
334 ✔
210
    assembly << instructionSet->lea(memoryOperand(operand), resultRegister);
334 ✔
211
    bindResult(resultRegister, resolve(resultName));
334 ✔
212
}
334 ✔
213

214
void StackMachine::dereference(std::string operandName, std::string lvalueName, std::string resultName) {
138 ✔
215
    auto& operand = resolve(operandName);
138 ✔
216
    // Use the register returned by the load path; global pointer homes are not register-bound.
217
    Register& pointerRegister = residesInMemory(operand) ? assignRegisterTo(operand) : operand.getAssignedRegister();
138 ✔
218
    Register& resultRegister = get64BitRegisterExcluding(pointerRegister);
138 ✔
219
    assembly << instructionSet->mov(MemoryOperand::at(pointerRegister, 0), resultRegister);
138 ✔
220
    bindResult(resultRegister, resolve(resultName));
138 ✔
221

222
    Register& lvalueRegister = get64BitRegisterExcluding(pointerRegister);
138 ✔
223
    assembly << instructionSet->mov(pointerRegister, lvalueRegister);
138 ✔
224
    lvalueRegister.assign(&resolve(lvalueName));
138 ✔
225
}
138 ✔
226

227
void StackMachine::unaryMinus(std::string operandName, std::string resultName) {
88 ✔
228
    auto& operand = resolve(operandName);
88 ✔
229
    if (residesInMemory(operand)) {
88 ✔
230
        Register& resultRegister = get64BitRegister();
82 ✔
231
        emitLoad(operand, resultRegister);
82 ✔
232
        assembly << instructionSet->neg(resultRegister);
82 ✔
233
        bindResult(resultRegister, resolve(resultName));
82 ✔
234
    } else {
235
        Register& operandRegister = operand.getAssignedRegister();
6 ✔
236
        Register& resultRegister = get64BitRegisterExcluding(operand.getAssignedRegister());
6 ✔
237
        assembly << instructionSet->mov(operandRegister, resultRegister);
6 ✔
238
        assembly << instructionSet->neg(resultRegister);
6 ✔
239
        bindResult(resultRegister, resolve(resultName));
6 ✔
240
    }
241
}
88 ✔
242

243
void StackMachine::assign(std::string operandName, std::string resultName) {
428 ✔
244
    auto& operand = resolve(operandName);
428 ✔
245
    auto& result = resolve(resultName);
428 ✔
246

247
    if (residesInMemory(operand) && residesInMemory(result)) {
428 ✔
248
        Register& reg = get64BitRegister();
292 ✔
249
        emitLoad(operand, reg);
292 ✔
250
        emitStore(reg, result);
292 ✔
251
    } else if (residesInMemory(operand)) {
136 ✔
UNCOV
252
        emitLoad(operand, result.getAssignedRegister());
×
253
    } else if (residesInMemory(result)) {
136 ✔
254
        emitStore(operand.getAssignedRegister(), result);
136 ✔
255
    } else {
256
        assembly << instructionSet->mov(operand.getAssignedRegister(), result.getAssignedRegister());
×
257
    }
258
}
428 ✔
259

260
void StackMachine::assignConstant(std::string constant, std::string resultName) {
2,442 ✔
261
    auto& result = resolve(resultName);
2,442 ✔
262
    if (residesInMemory(result)) {
2,442 ✔
263
        assembly << instructionSet->mov(constant, memoryOperand(result));
2,442 ✔
264
    } else {
UNCOV
265
        assembly << instructionSet->mov(constant, result.getAssignedRegister());
×
266
    }
267
}
2,442 ✔
268

269
void StackMachine::lvalueAssign(std::string operandName, std::string resultName) {
86 ✔
270
    auto& operand = resolve(operandName);
86 ✔
271
    auto& result = resolve(resultName);
86 ✔
272

273
    Register& operandRegister = residesInMemory(operand) ? assignRegisterTo(operand) : operand.getAssignedRegister();
86 ✔
274
    Register& resultRegister = residesInMemory(result) ? assignRegisterExcluding(result, operandRegister) : result.getAssignedRegister();
86 ✔
275
    assembly << instructionSet->mov(operandRegister, MemoryOperand::at(resultRegister, 0));
86 ✔
276
}
86 ✔
277

278
void StackMachine::procedureArgument(std::string argumentName) {
2,002 ✔
279
    auto argument = &resolve(argumentName);
2,002 ✔
280
    if (integerArguments.size() < registers->getIntegerArgumentRegisters().size()) {
2,002 ✔
281
        integerArguments.push_back(argument);
1,940 ✔
282
    } else {
283
        stackArguments.insert(stackArguments.begin(), argument);
62 ✔
284
    }
285
}
2,002 ✔
286

287
void StackMachine::callProcedure(std::string procedureName) {
920 ✔
288
    for (std::size_t i = 0; i < integerArguments.size(); ++i) {
2,860 ✔
289
        assignRegisterToSymbol(*registers->getIntegerArgumentRegisters()[i], *integerArguments[i]);
1,940 ✔
290
    }
291
    storeRegisterValue(registers->getRetrievalRegister());
920 ✔
292
    spillCallerSavedRegisters();
920 ✔
293
    int argumentOffset{0};
920 ✔
294
    // System V AMD64: RSP must be 16-byte aligned before call. Without stack args we are
295
    // aligned; each stack arg is 8 bytes, so an odd count needs 8 bytes of padding.
296
    if (stackArguments.size() % 2 == 1) {
920 ✔
297
        assembly << instructionSet->sub(registers->getStackPointer(), MACHINE_WORD_SIZE);
10 ✔
298
        argumentOffset += MACHINE_WORD_SIZE;
10 ✔
299
    }
300
    for (auto argument : stackArguments) {
982 ✔
301
        pushProcedureArgument(*argument, argumentOffset);
62 ✔
302
        argumentOffset += MACHINE_WORD_SIZE;
62 ✔
303
    }
304
    integerArguments.clear();
920 ✔
305
    stackArguments.clear();
920 ✔
306
    // AL must hold the number of vector registers used for variadic calls (System V AMD64).
307
    // This compiler only passes integer args, so set AL to 0 via xor rax, rax.
308
    auto& retrievalRegister = registers->getRetrievalRegister();
920 ✔
309
    assembly << instructionSet->xor_(retrievalRegister, retrievalRegister);
920 ✔
310
    assembly << instructionSet->call(procedureName);
920 ✔
311
    if (argumentOffset) {
920 ✔
312
        assembly << instructionSet->add(registers->getStackPointer(), argumentOffset);
16 ✔
313
    }
314
}
920 ✔
315

316
void StackMachine::pushProcedureArgument(Value& symbolToPush, int argumentOffset) {
62 ✔
317
    if (residesInMemory(symbolToPush)) {
62 ✔
318
        Register& reg = get64BitRegister();
62 ✔
319
        // Stack-pointer homes move as call args are pushed; base-pointer / global do not.
320
        Address home = addressOf(symbolToPush);
62 ✔
321
        if (!home.isGlobal() && home.frameBase() == FrameBase::StackPointer) {
62 ✔
322
            home = Address::frame(home.frameBase(), home.offsetBytes() + argumentOffset, home.sizeBytes());
62 ✔
323
        }
324
        assembly << instructionSet->mov(memoryOperand(home), reg);
62 ✔
325
        assembly << instructionSet->push(reg);
62 ✔
326
    } else {
62 ✔
UNCOV
327
        assembly << instructionSet->push(symbolToPush.getAssignedRegister());
×
328
    }
329
}
62 ✔
330

331
void StackMachine::returnFromProcedure(std::string returnSymbolName) {
488 ✔
332
    if (!returnSymbolName.empty()) {
488 ✔
333
        Value& returnSymbol = resolve(returnSymbolName);
444 ✔
334
        if (residesInMemory(returnSymbol)) {
444 ✔
335
            emitLoad(returnSymbol, registers->getRetrievalRegister());
430 ✔
336
        } else if (&registers->getRetrievalRegister() != &returnSymbol.getAssignedRegister()) {
14 ✔
337
            assembly << instructionSet->mov(returnSymbol.getAssignedRegister(), registers->getRetrievalRegister());
10 ✔
338
        }
339
    }
340
    popCalleeSavedRegisters();
488 ✔
341
    assembly << instructionSet->leave();
488 ✔
342
    assembly << instructionSet->ret();
488 ✔
343
}
488 ✔
344

345
void StackMachine::retrieveProcedureReturnValue(std::string returnSymbolName) {
910 ✔
346
    Value& returnSymbol = resolve(returnSymbolName);
910 ✔
347
    emitStore(registers->getRetrievalRegister(), returnSymbol);
910 ✔
348
}
910 ✔
349

350
void StackMachine::xorCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
351
    Value& leftOperand = resolve(leftOperandName);
10 ✔
352
    Value& rightOperand = resolve(rightOperandName);
10 ✔
353
    Register& resultRegister = get64BitRegister();
10 ✔
354

355
    if (residesInMemory(leftOperand)) {
10 ✔
356
        emitLoad(leftOperand, resultRegister);
8 ✔
357
    } else {
358
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2 ✔
359
    }
360
    if (residesInMemory(rightOperand)) {
10 ✔
361
        assembly << instructionSet->xor_(memoryOperand(rightOperand), resultRegister);
10 ✔
362
    } else {
UNCOV
363
        assembly << instructionSet->xor_(rightOperand.getAssignedRegister(), resultRegister);
×
364
    }
365
    bindResult(resultRegister, resolve(resultName));
10 ✔
366
}
10 ✔
367

368
void StackMachine::orCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
369
    Value& leftOperand = resolve(leftOperandName);
10 ✔
370
    Value& rightOperand = resolve(rightOperandName);
10 ✔
371
    Register& resultRegister = get64BitRegister();
10 ✔
372

373
    if (residesInMemory(leftOperand)) {
10 ✔
374
        emitLoad(leftOperand, resultRegister);
8 ✔
375
    } else {
376
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2 ✔
377
    }
378
    if (residesInMemory(rightOperand)) {
10 ✔
379
        assembly << instructionSet->or_(memoryOperand(rightOperand), resultRegister);
10 ✔
380
    } else {
UNCOV
381
        assembly << instructionSet->or_(rightOperand.getAssignedRegister(), resultRegister);
×
382
    }
383
    bindResult(resultRegister, resolve(resultName));
10 ✔
384
}
10 ✔
385

386
void StackMachine::andCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
387
    Value& leftOperand = resolve(leftOperandName);
10 ✔
388
    Value& rightOperand = resolve(rightOperandName);
10 ✔
389
    Register& resultRegister = get64BitRegister();
10 ✔
390

391
    if (residesInMemory(leftOperand)) {
10 ✔
392
        emitLoad(leftOperand, resultRegister);
8 ✔
393
    } else {
394
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2 ✔
395
    }
396
    if (residesInMemory(rightOperand)) {
10 ✔
397
        assembly << instructionSet->and_(memoryOperand(rightOperand), resultRegister);
10 ✔
398
    } else {
UNCOV
399
        assembly << instructionSet->and_(rightOperand.getAssignedRegister(), resultRegister);
×
400
    }
401
    bindResult(resultRegister, resolve(resultName));
10 ✔
402
}
10 ✔
403

404
void StackMachine::add(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
92 ✔
405
    Value& leftOperand = resolve(leftOperandName);
92 ✔
406
    Value& rightOperand = resolve(rightOperandName);
92 ✔
407
    Register& resultRegister = get64BitRegister();
92 ✔
408

409
    if (residesInMemory(leftOperand)) {
92 ✔
410
        emitLoad(leftOperand, resultRegister);
66 ✔
411
    } else {
412
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
26 ✔
413
    }
414
    if (residesInMemory(rightOperand)) {
92 ✔
415
        assembly << instructionSet->add(memoryOperand(rightOperand), resultRegister);
76 ✔
416
    } else {
417
        assembly << instructionSet->add(rightOperand.getAssignedRegister(), resultRegister);
16 ✔
418
    }
419
    bindResult(resultRegister, resolve(resultName));
92 ✔
420
}
92 ✔
421

422
void StackMachine::sub(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
30 ✔
423
    Value& leftOperand = resolve(leftOperandName);
30 ✔
424
    Value& rightOperand = resolve(rightOperandName);
30 ✔
425
    Register& resultRegister = get64BitRegister();
30 ✔
426

427
    if (residesInMemory(leftOperand)) {
30 ✔
428
        emitLoad(leftOperand, resultRegister);
22 ✔
429
    } else {
430
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
8 ✔
431
    }
432
    if (residesInMemory(rightOperand)) {
30 ✔
433
        assembly << instructionSet->sub(memoryOperand(rightOperand), resultRegister);
26 ✔
434
    } else {
435
        assembly << instructionSet->sub(rightOperand.getAssignedRegister(), resultRegister);
4 ✔
436
    }
437
    bindResult(resultRegister, resolve(resultName));
30 ✔
438
}
30 ✔
439

440
void StackMachine::mul(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
16 ✔
441
    Value& leftOperand = resolve(leftOperandName);
16 ✔
442
    Value& rightOperand = resolve(rightOperandName);
16 ✔
443
    Value& result = resolve(resultName);
16 ✔
444

445
    if (result.getType() != Type::INTEGRAL) {
16 ✔
UNCOV
446
        throw std::runtime_error{"multiplication of non integers is not implemented"};
×
447
    }
448

449
    Register& multiplicationRegister = registers->getMultiplicationRegister();
16 ✔
450
    assignRegisterToSymbol(multiplicationRegister, leftOperand);
16 ✔
451
    // imul writes RDX:RAX; spill RDX if it holds a live value (e.g. pointer for *p *= ...)
452
    storeRegisterValue(registers->getRemainderRegister());
16 ✔
453
    if (residesInMemory(rightOperand)) {
16 ✔
454
        assembly << instructionSet->imul(memoryOperand(rightOperand));
16 ✔
455
    } else {
UNCOV
456
        assembly << instructionSet->imul(rightOperand.getAssignedRegister());
×
457
    }
458
    bindResult(multiplicationRegister, result);
16 ✔
459
}
16 ✔
460

461
void StackMachine::div(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
462
    Value& leftOperand = resolve(leftOperandName);
10 ✔
463
    Value& rightOperand = resolve(rightOperandName);
10 ✔
464
    Value& result = resolve(resultName);
10 ✔
465

466
    if (result.getType() != Type::INTEGRAL) {
10 ✔
UNCOV
467
        throw std::runtime_error{"division of non integer types is not implemented"};
×
468
    }
469

470
    Register& multiplicationRegister = registers->getMultiplicationRegister();
10 ✔
471
    assignRegisterToSymbol(multiplicationRegister, leftOperand);
10 ✔
472
    storeRegisterValue(registers->getRemainderRegister());
10 ✔
473
    assembly << instructionSet->xor_(registers->getRemainderRegister(), registers->getRemainderRegister());
10 ✔
474
    if (residesInMemory(rightOperand)) {
10 ✔
475
        assembly << instructionSet->idiv(memoryOperand(rightOperand));
10 ✔
476
    } else {
UNCOV
477
        assembly << instructionSet->idiv(rightOperand.getAssignedRegister());
×
478
    }
479
    bindResult(multiplicationRegister, result);
10 ✔
480
}
10 ✔
481

482
void StackMachine::mod(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
483
    Value& leftOperand = resolve(leftOperandName);
10 ✔
484
    Value& rightOperand = resolve(rightOperandName);
10 ✔
485
    Value& result = resolve(resultName);
10 ✔
486

487
    if (result.getType() != Type::INTEGRAL) {
10 ✔
UNCOV
488
        throw std::runtime_error{"modular division of non integer types is not implemented"};
×
489
    }
490

491
    Register& multiplicationRegister = registers->getMultiplicationRegister();
10 ✔
492
    assignRegisterToSymbol(multiplicationRegister, leftOperand);
10 ✔
493
    storeRegisterValue(registers->getRemainderRegister());
10 ✔
494
    assembly << instructionSet->xor_(registers->getRemainderRegister(), registers->getRemainderRegister());
10 ✔
495
    if (residesInMemory(rightOperand)) {
10 ✔
496
        assembly << instructionSet->idiv(memoryOperand(rightOperand));
10 ✔
497
    } else {
UNCOV
498
        assembly << instructionSet->idiv(rightOperand.getAssignedRegister());
×
499
    }
500
    bindResult(registers->getRemainderRegister(), result);
10 ✔
501
}
10 ✔
502

503
void StackMachine::inc(std::string operandName) {
40 ✔
504
    Value& operand = resolve(operandName);
40 ✔
505
    if (residesInMemory(operand)) {
40 ✔
506
        assembly << instructionSet->inc(memoryOperand(operand));
12 ✔
507
    } else {
508
        assembly << instructionSet->inc(operand.getAssignedRegister());
28 ✔
509
    }
510
}
40 ✔
511

512
void StackMachine::dec(std::string operandName) {
12 ✔
513
    Value& operand = resolve(operandName);
12 ✔
514
    if (residesInMemory(operand)) {
12 ✔
515
        assembly << instructionSet->dec(memoryOperand(operand));
4 ✔
516
    } else {
517
        assembly << instructionSet->dec(operand.getAssignedRegister());
8 ✔
518
    }
519
}
12 ✔
520

521
void StackMachine::shiftBy(std::string leftOperandName, std::string rightOperandName, std::string resultName,
38 ✔
522
        std::string (InstructionSet::*emitShift)(const Register&) const) {
523
    // Count must live in %cl (RCX) and be tracked so the value is not placed in RCX.
524
    Register& counterRegister = getCounterRegister();
38 ✔
525
    Value& rightOperand = resolve(rightOperandName);
38 ✔
526
    if (residesInMemory(rightOperand)) {
38 ✔
527
        emitLoad(rightOperand, counterRegister);
38 ✔
UNCOV
528
    } else if (&counterRegister != &rightOperand.getAssignedRegister()) {
×
UNCOV
529
        assembly << instructionSet->mov(rightOperand.getAssignedRegister(), counterRegister);
×
UNCOV
530
        storeRegisterValue(rightOperand.getAssignedRegister());
×
531
    }
532
    // Count in %cl is scratch only; never register-cache a global home on RCX.
533
    if (!addressOf(rightOperand).isGlobal()) {
38 ✔
534
        counterRegister.assign(&rightOperand);
32 ✔
535
    }
536

537
    Value& leftOperand = resolve(leftOperandName);
38 ✔
538
    Register& resultRegister = get64BitRegisterExcluding(counterRegister);
38 ✔
539
    assignRegisterToSymbol(resultRegister, leftOperand);
38 ✔
540
    assembly << (instructionSet.get()->*emitShift)(resultRegister);
38 ✔
541
    Value& result = resolve(resultName);
38 ✔
542
    bindResult(resultRegister, result);
38 ✔
543
}
38 ✔
544

545
void StackMachine::shl(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
20 ✔
546
    shiftBy(leftOperandName, rightOperandName, resultName, &InstructionSet::shl);
20 ✔
547
}
20 ✔
548

549
void StackMachine::shr(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
18 ✔
550
    shiftBy(leftOperandName, rightOperandName, resultName, &InstructionSet::shr);
18 ✔
551
}
18 ✔
552

553
void StackMachine::storeRegisterValue(Register& reg) {
22,798 ✔
554
    if (reg.containsUnstoredValue()) {
22,798 ✔
555
        emitStore(reg, *reg.getValue());
1,130 ✔
556
        reg.free();
1,130 ✔
557
    }
558
}
22,798 ✔
559

560
void StackMachine::emptyGeneralPurposeRegisters() {
934 ✔
561
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
14,010 ✔
562
        reg->free();
13,076 ✔
563
    }
934 ✔
564
}
934 ✔
565

566
void StackMachine::pushCalleeSavedRegisters() { pushRegisters(registers->getCalleeSavedRegisters(), calleeSavedRegisters); }
472 ✔
567

568
void StackMachine::popCalleeSavedRegisters() { popRegisters(calleeSavedRegisters); }
488 ✔
569

570
void StackMachine::pushRegisters(std::vector<Register*> source, std::vector<Register*>& destination) {
472 ✔
571
    for (auto& reg : source) {
2,832 ✔
572
        pushRegister(*reg, destination);
2,360 ✔
573
    }
574
}
472 ✔
575

576
void StackMachine::popRegisters(std::vector<Register*> registers) {
488 ✔
577
    for (auto& reg : registers) {
2,918 ✔
578
        assembly << instructionSet->pop(*reg);
2,430 ✔
579
    }
580
}
488 ✔
581

582
void StackMachine::pushRegister(Register& reg, std::vector<Register*>& registers) {
2,360 ✔
583
    registers.insert(registers.begin(), &reg);
2,360 ✔
584
    assembly << instructionSet->push(reg);
2,360 ✔
585
}
2,360 ✔
586

587
void StackMachine::storeInMemory(Value& symbol) {
334 ✔
588
    if (!symbol.isStored()) {
334 ✔
UNCOV
589
        storeRegisterValue(symbol.getAssignedRegister());
×
590
    }
591
}
334 ✔
592

593
Address StackMachine::spillSlotAddress(const Value& symbol) const {
4,694 ✔
594
    int offset = symbol.getIndex() * MACHINE_WORD_SIZE
4,694 ✔
595
            + static_cast<int>(calleeSavedRegisters.size()) * MACHINE_WORD_SIZE;
4,694 ✔
596
    return Address::frame(FrameBase::StackPointer, offset, symbol.getSizeInBytes());
4,694 ✔
597
}
598

599
void StackMachine::registerFrameHome(const std::string& name, Address address) {
4,724 ✔
600
    auto inserted = frameHomes.emplace(name, std::move(address)).second;
4,724 ✔
601
    assert(inserted && "duplicate frame home registration");
4,724 ✔
602
}
4,724 ✔
603

604
bool StackMachine::residesInMemory(const Value& symbol) const {
7,342 ✔
605
    return addressOf(symbol).isGlobal() || symbol.isStored();
7,342 ✔
606
}
607

608
Address StackMachine::addressOf(const Value& symbol) const {
17,064 ✔
609
    const std::string& name = symbol.getName();
17,064 ✔
610
    auto frame = frameHomes.find(name);
17,064 ✔
611
    if (frame != frameHomes.end()) {
17,064 ✔
612
        return frame->second;
16,462 ✔
613
    }
614
    auto global = globalHomes.find(name);
602 ✔
615
    if (global != globalHomes.end()) {
602 ✔
616
        return global->second;
584 ✔
617
    }
618
    // No registered home: a temporary. Its spill slot is derived from Value::index, which the
619
    // code generator must keep consistent with the value's position among the frame's locals.
620
    return spillSlotAddress(symbol);
18 ✔
621
}
17,064 ✔
622

623
MemoryOperand StackMachine::memoryOperand(const Address& address) const {
8,636 ✔
624
    if (address.isGlobal()) {
8,636 ✔
625
        return MemoryOperand::global(address.label());
292 ✔
626
    }
627
    const Register& base = address.frameBase() == FrameBase::BasePointer ?
8,344 ✔
628
            registers->getBasePointer() : registers->getStackPointer();
8,344 ✔
629
    return MemoryOperand::at(base, address.offsetBytes());
8,344 ✔
630
}
631

632
MemoryOperand StackMachine::memoryOperand(const Value& symbol) const {
8,574 ✔
633
    return memoryOperand(addressOf(symbol));
8,574 ✔
634
}
635

636
Register& StackMachine::get64BitRegister() {
1,176 ✔
637
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
2,046 ✔
638
        if (!reg->containsUnstoredValue()) {
2,036 ✔
639
            return *reg;
1,166 ✔
640
        }
641
    }
1,176 ✔
642
    Register& reg = **registers->getGeneralPurposeRegisters().begin();
10 ✔
643
    storeRegisterValue(reg);
10 ✔
644
    return reg;
10 ✔
645
}
646

647
Register& StackMachine::get64BitRegisterExcluding(Register& registerToExclude) {
328 ✔
648
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
1,830 ✔
649
        if (reg != &registerToExclude && !reg->containsUnstoredValue()) {
1,802 ✔
650
            return *reg;
300 ✔
651
        }
652
    }
328 ✔
653
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
44 ✔
654
        if (reg != &registerToExclude) {
44 ✔
655
            storeRegisterValue(*reg);
28 ✔
656
            return *reg;
28 ✔
657
        }
658
    }
28 ✔
UNCOV
659
    throw std::runtime_error{"unable to get a free register"};
×
660
}
661

662
Register& StackMachine::getCounterRegister() {
38 ✔
663
    Register& counter = registers->getCounterRegister();
38 ✔
664
    storeRegisterValue(counter);
38 ✔
665
    return counter;
38 ✔
666
}
667

668
Register& StackMachine::assignRegisterTo(Value& symbol) {
206 ✔
669
    Register& reg = get64BitRegister();
206 ✔
670
    loadWithoutBinding(symbol, reg);
206 ✔
671
    // Bind only non-global homes; globals stay Address-only (scratch in reg).
672
    if (!addressOf(symbol).isGlobal()) {
206 ✔
673
        reg.assign(&symbol);
196 ✔
674
    }
675
    return reg;
206 ✔
676
}
677

678
Register& StackMachine::assignRegisterExcluding(Value& symbol, Register& registerToExclude) {
8 ✔
679
    Register& reg = get64BitRegisterExcluding(registerToExclude);
8 ✔
680
    loadWithoutBinding(symbol, reg);
8 ✔
681
    if (!addressOf(symbol).isGlobal()) {
8 ✔
682
        reg.assign(&symbol);
8 ✔
683
    }
684
    return reg;
8 ✔
685
}
686

687
void StackMachine::setScope(std::vector<Value> variables) {
16 ✔
688
    for (auto& var : variables) {
64 ✔
689
        scopeValues.insert({var.getName(), var});
48 ✔
690
        registerFrameHome(var.getName(), spillSlotAddress(var));
48 ✔
691
    }
692
}
16 ✔
693

694
Value& StackMachine::resolve(const std::string& name) {
9,524 ✔
695
    auto local = scopeValues.find(name);
9,524 ✔
696
    if (local != scopeValues.end()) {
9,524 ✔
697
        return local->second;
9,232 ✔
698
    }
699
    return globals.at(name);
292 ✔
700
}
701

702

703

704

705
void StackMachine::fieldAddress(std::string baseName, int offsetBytes, std::string resultName, bool baseIsPointer) {
48 ✔
706
    auto& base = resolve(baseName);
48 ✔
707
    Register& addrReg = get64BitRegister();
48 ✔
708
    if (baseIsPointer) {
48 ✔
709
        // Arrow: base holds a pointer value (object address).
710
        if (residesInMemory(base)) {
8 ✔
711
            emitLoad(base, addrReg);
8 ✔
712
        } else {
NEW
UNCOV
713
            assembly << instructionSet->mov(base.getAssignedRegister(), addrReg);
×
714
        }
715
    } else {
716
        // Dot: base is the object; take its address.
717
        assembly << instructionSet->lea(memoryOperand(base), addrReg);
40 ✔
718
    }
719
    if (offsetBytes) {
48 ✔
720
        assembly << instructionSet->add(addrReg, offsetBytes);
24 ✔
721
    }
722
    bindResult(addrReg, resolve(resultName));
48 ✔
723
}
48 ✔
724

725
} // namespace codegen
726

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc