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

rieske / trans / 29902648358

22 Jul 2026 08:04AM UTC coverage: 91.671% (+0.5%) from 91.142%
29902648358

Pull #53

github

rieske
Fuzz-test and fix the backend
Pull Request #53: Fuzz-test and fix the backend

128 of 134 new or added lines in 10 files covered. (95.52%)

5514 of 6015 relevant lines covered (91.67%)

379841.41 hits per line

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

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

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

6
#include "InstructionSet.h"
7

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

13
namespace codegen {
14

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

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

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

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

38
    for (auto& value : values) {
5,338✔
39
        scopeValues.insert({value.getName(), value});
4,808✔
40
    }
41
    std::size_t integerArgumentRegisterIndex{0};
530✔
42
    std::size_t localIndex{scopeValues.size()};
530✔
43
    int argumentIndex{0};
530✔
44
    for (auto& argument : arguments) {
722✔
45
        if (argument.getType() == Type::INTEGRAL && integerArgumentRegisterIndex < registers->getIntegerArgumentRegisters().size()) {
192✔
46
            Value registerArgument{argument.getName(), static_cast<int>(localIndex), argument.getType(), argument.getSizeInBytes()};
144✔
47
            scopeValues.insert({argument.getName(), registerArgument});
144✔
48
            registers->getIntegerArgumentRegisters()[integerArgumentRegisterIndex]->assign(&resolve(argument.getName()));
144✔
49
            ++integerArgumentRegisterIndex;
144✔
50
            ++localIndex;
144✔
51
        } else {
144✔
52
            Value stackArgument{argument.getName(), argumentIndex, argument.getType(), argument.getSizeInBytes()};
48✔
53
            scopeValues.insert({argument.getName(), stackArgument});
48✔
54
            // Stack args live at fixed base-pointer offsets; independent of callee-saved size.
55
            registerFrameHome(argument.getName(), Address::frame(FrameBase::BasePointer,
96✔
56
                    (argumentIndex + 2) * MACHINE_WORD_SIZE, argument.getSizeInBytes()));
48✔
57
            ++argumentIndex;
48✔
58
        }
48✔
59
    }
60
    int savedRegistersStack = registers->getCalleeSavedRegisters().size() * MACHINE_WORD_SIZE;
530✔
61
    localVariableStackSize = (scopeValues.size() - argumentIndex) * MACHINE_WORD_SIZE;
530✔
62
    int stackSize = savedRegistersStack + localVariableStackSize;
530✔
63
    if (stackSize % STACK_ALIGNMENT) {
530✔
64
        assembly << instructionSet->sub(registers->getStackPointer(), localVariableStackSize + MACHINE_WORD_SIZE);
250✔
65
    } else {
66
        assembly << instructionSet->sub(registers->getStackPointer(), localVariableStackSize);
280✔
67
    }
68

69
    pushCalleeSavedRegisters();
530✔
70
    // Stack-pointer locals / reg-arg spill slots include callee-saved space.
71
    for (const auto& entry : scopeValues) {
5,530✔
72
        if (frameHomes.count(entry.first)) {
5,000✔
73
            continue;
48✔
74
        }
75
        registerFrameHome(entry.first, spillSlotAddress(entry.second));
4,952✔
76
    }
77
}
530✔
78

79
void StackMachine::endProcedure() {
520✔
80
    emptyGeneralPurposeRegisters();
520✔
81
    scopeValues.clear();
520✔
82
    frameHomes.clear();
520✔
83
    calleeSavedRegisters.clear();
520✔
84
}
520✔
85

86
void StackMachine::label(std::string name) {
698✔
87
    spillGeneralPurposeRegisters();
698✔
88
    assembly.label(instructionSet->label(name));
698✔
89
}
698✔
90

91
void StackMachine::jump(JumpCondition jumpCondition, std::string label) {
684✔
92
    // Spill on every outgoing edge. Conditional jumps used to skip this, so a branch
93
    // to a join label could skip the spill that label() emits only on fall-through —
94
    // leaving live values (e.g. argument registers) in regs while later code reloads
95
    // them from unsaved stack slots. Repro: `int f(int a){ if(0); return a; }`.
96
    spillGeneralPurposeRegisters();
684✔
97
    switch (jumpCondition) {
684✔
98
    case JumpCondition::IF_EQUAL:
188✔
99
        assembly << instructionSet->je(label);
188✔
100
        break;
188✔
101
    case JumpCondition::IF_NOT_EQUAL:
22✔
102
        assembly << instructionSet->jne(label);
22✔
103
        break;
22✔
104
    case JumpCondition::IF_ABOVE:
40✔
105
        assembly << instructionSet->jg(label);
40✔
106
        break;
40✔
107
    case JumpCondition::IF_BELOW:
66✔
108
        assembly << instructionSet->jl(label);
66✔
109
        break;
66✔
110
    case JumpCondition::IF_ABOVE_OR_EQUAL:
24✔
111
        assembly << instructionSet->jge(label);
24✔
112
        break;
24✔
113
    case JumpCondition::IF_BELOW_OR_EQUAL:
28✔
114
        assembly << instructionSet->jle(label);
28✔
115
        break;
28✔
116
    case JumpCondition::UNCONDITIONAL:
316✔
117
    default:
118
        assembly << instructionSet->jmp(label);
316✔
119
    }
120
}
684✔
121

122
void StackMachine::spillGeneralPurposeRegisters() {
1,382✔
123
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
20,730✔
124
        storeRegisterValue(*reg);
19,348✔
125
    }
1,382✔
126
}
1,382✔
127

128
void StackMachine::spillCallerSavedRegisters() {
992✔
129
    for (auto& reg : registers->getCallerSavedRegisters()) {
8,928✔
130
        storeRegisterValue(*reg);
7,936✔
131
    }
992✔
132
}
992✔
133

134
void StackMachine::emitLoad(Value& symbol, Register& dest) {
3,058✔
135
    assembly << instructionSet->mov(memoryOperand(symbol), dest);
3,058✔
136
}
3,058✔
137

138
void StackMachine::loadWithoutBinding(Value& symbol, Register& dest) {
1,958✔
139
    emitLoad(symbol, dest);
1,958✔
140
}
1,958✔
141

142
void StackMachine::emitStore(Register& source, Value& symbol) {
2,588✔
143
    assembly << instructionSet->mov(source, memoryOperand(symbol));
2,588✔
144
}
2,588✔
145

146
// Bind a freshly computed result to its destination symbol. Global homes are Address-only:
147
// commit to memory; never attach a register to the global Value (loads use scratch only).
148
// Locals/temps use register residence and lazy write-back.
149
void StackMachine::bindResult(Register& reg, Value& result) {
788✔
150
    if (addressOf(result).isGlobal()) {
788✔
151
        emitStore(reg, result);
24✔
152
        assert(result.isStored() && "global Value must not be register-linked");
24✔
153
        return;
24✔
154
    }
155
    reg.assign(&result);
764✔
156
}
157

158
void StackMachine::assignRegisterToSymbol(Register& reg, Value& symbol) {
2,154✔
159
    // Always load without binding when the symbol is memory-resident (includes all globals:
160
    // their homes are Address-only; never reg.assign the global Value).
161
    if (residesInMemory(symbol)) {
2,154✔
162
        storeRegisterValue(reg);
1,744✔
163
        loadWithoutBinding(symbol, reg);
1,744✔
164
    } else if (&reg != &symbol.getAssignedRegister()) {
410✔
165
        storeRegisterValue(reg);
402✔
166
        Register& valueRegister = symbol.getAssignedRegister();
402✔
167
        storeRegisterValue(valueRegister);
402✔
168
        assembly << instructionSet->mov(valueRegister, reg);
402✔
169
    }
170
}
2,154✔
171

172
void StackMachine::compare(std::string leftSymbolName, std::string rightSymbolName) {
196✔
173
    auto& leftSymbol = resolve(leftSymbolName);
196✔
174
    auto& rightSymbol = resolve(rightSymbolName);
196✔
175

176
    if (residesInMemory(leftSymbol) && residesInMemory(rightSymbol)) {
196✔
177
        Register& rightSymbolRegister = assignRegisterTo(rightSymbol);
138✔
178
        assembly << instructionSet->cmp(memoryOperand(leftSymbol), rightSymbolRegister);
138✔
179
    } else if (residesInMemory(leftSymbol)) {
58✔
180
        assembly << instructionSet->cmp(memoryOperand(leftSymbol), rightSymbol.getAssignedRegister());
16✔
181
    } else if (residesInMemory(rightSymbol)) {
42✔
182
        assembly << instructionSet->cmp(leftSymbol.getAssignedRegister(), memoryOperand(rightSymbol));
22✔
183
    } else {
184
        assembly << instructionSet->cmp(leftSymbol.getAssignedRegister(), rightSymbol.getAssignedRegister());
20✔
185
    }
186
}
196✔
187

188
void StackMachine::zeroCompare(std::string symbolName) {
172✔
189
    auto& symbol = resolve(symbolName);
172✔
190
    if (residesInMemory(symbol)) {
172✔
191
        assembly << instructionSet->cmp(memoryOperand(symbol), 0);
160✔
192
    } else {
193
        assembly << instructionSet->cmp(symbol.getAssignedRegister(), 0);
12✔
194
    }
195
}
172✔
196

197
void StackMachine::addressOf(std::string operandName, std::string resultName) {
328✔
198
    auto& operand = resolve(operandName);
328✔
199
    storeInMemory(operand);
328✔
200
    Register& resultRegister = get64BitRegister();
328✔
201
    assembly << instructionSet->lea(memoryOperand(operand), resultRegister);
328✔
202
    bindResult(resultRegister, resolve(resultName));
328✔
203
}
328✔
204

205
void StackMachine::dereference(std::string operandName, std::string lvalueName, std::string resultName) {
92✔
206
    auto& operand = resolve(operandName);
92✔
207
    // Use the register returned by the load path; global pointer homes are not register-bound.
208
    Register& pointerRegister = residesInMemory(operand) ? assignRegisterTo(operand) : operand.getAssignedRegister();
92✔
209
    Register& resultRegister = get64BitRegisterExcluding(pointerRegister);
92✔
210
    assembly << instructionSet->mov(MemoryOperand::at(pointerRegister, 0), resultRegister);
92✔
211
    bindResult(resultRegister, resolve(resultName));
92✔
212

213
    Register& lvalueRegister = get64BitRegisterExcluding(pointerRegister);
92✔
214
    assembly << instructionSet->mov(pointerRegister, lvalueRegister);
92✔
215
    lvalueRegister.assign(&resolve(lvalueName));
92✔
216
}
92✔
217

218
void StackMachine::unaryMinus(std::string operandName, std::string resultName) {
94✔
219
    auto& operand = resolve(operandName);
94✔
220
    if (residesInMemory(operand)) {
94✔
221
        Register& resultRegister = get64BitRegister();
88✔
222
        emitLoad(operand, resultRegister);
88✔
223
        assembly << instructionSet->neg(resultRegister);
88✔
224
        bindResult(resultRegister, resolve(resultName));
88✔
225
    } else {
226
        Register& operandRegister = operand.getAssignedRegister();
6✔
227
        Register& resultRegister = get64BitRegisterExcluding(operand.getAssignedRegister());
6✔
228
        assembly << instructionSet->mov(operandRegister, resultRegister);
6✔
229
        assembly << instructionSet->neg(resultRegister);
6✔
230
        bindResult(resultRegister, resolve(resultName));
6✔
231
    }
232
}
94✔
233

234
void StackMachine::unaryNot(std::string operandName, std::string resultName) {
18✔
235
    auto& operand = resolve(operandName);
18✔
236
    if (residesInMemory(operand)) {
18✔
237
        Register& resultRegister = get64BitRegister();
16✔
238
        emitLoad(operand, resultRegister);
16✔
239
        assembly << instructionSet->not_(resultRegister);
16✔
240
        bindResult(resultRegister, resolve(resultName));
16✔
241
    } else {
242
        Register& operandRegister = operand.getAssignedRegister();
2✔
243
        Register& resultRegister = get64BitRegisterExcluding(operand.getAssignedRegister());
2✔
244
        assembly << instructionSet->mov(operandRegister, resultRegister);
2✔
245
        assembly << instructionSet->not_(resultRegister);
2✔
246
        bindResult(resultRegister, resolve(resultName));
2✔
247
    }
248
}
18✔
249

250
void StackMachine::assign(std::string operandName, std::string resultName) {
484✔
251
    auto& operand = resolve(operandName);
484✔
252
    auto& result = resolve(resultName);
484✔
253

254
    if (residesInMemory(operand) && residesInMemory(result)) {
484✔
255
        Register& reg = get64BitRegister();
332✔
256
        emitLoad(operand, reg);
332✔
257
        emitStore(reg, result);
332✔
258
    } else if (residesInMemory(operand)) {
152✔
259
        emitLoad(operand, result.getAssignedRegister());
×
260
    } else if (residesInMemory(result)) {
152✔
261
        emitStore(operand.getAssignedRegister(), result);
152✔
262
    } else {
263
        assembly << instructionSet->mov(operand.getAssignedRegister(), result.getAssignedRegister());
×
264
    }
265
}
484✔
266

267
void StackMachine::assignConstant(std::string constant, std::string resultName) {
2,700✔
268
    auto& result = resolve(resultName);
2,700✔
269
    if (residesInMemory(result)) {
2,700✔
270
        assembly << instructionSet->mov(constant, memoryOperand(result));
2,700✔
271
    } else {
272
        assembly << instructionSet->mov(constant, result.getAssignedRegister());
×
273
    }
274
}
2,700✔
275

276
void StackMachine::lvalueAssign(std::string operandName, std::string resultName) {
62✔
277
    auto& operand = resolve(operandName);
62✔
278
    auto& result = resolve(resultName);
62✔
279

280
    Register& operandRegister = residesInMemory(operand) ? assignRegisterTo(operand) : operand.getAssignedRegister();
62✔
281
    Register& resultRegister = residesInMemory(result) ? assignRegisterExcluding(result, operandRegister) : result.getAssignedRegister();
62✔
282
    assembly << instructionSet->mov(operandRegister, MemoryOperand::at(resultRegister, 0));
62✔
283
}
62✔
284

285
void StackMachine::procedureArgument(std::string argumentName) {
2,132✔
286
    auto argument = &resolve(argumentName);
2,132✔
287
    if (integerArguments.size() < registers->getIntegerArgumentRegisters().size()) {
2,132✔
288
        integerArguments.push_back(argument);
2,070✔
289
    } else {
290
        stackArguments.insert(stackArguments.begin(), argument);
62✔
291
    }
292
}
2,132✔
293

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

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

338
void StackMachine::returnFromProcedure(std::string returnSymbolName) {
550✔
339
    if (!returnSymbolName.empty()) {
550✔
340
        Value& returnSymbol = resolve(returnSymbolName);
506✔
341
        if (residesInMemory(returnSymbol)) {
506✔
342
            emitLoad(returnSymbol, registers->getRetrievalRegister());
486✔
343
        } else if (&registers->getRetrievalRegister() != &returnSymbol.getAssignedRegister()) {
20✔
344
            assembly << instructionSet->mov(returnSymbol.getAssignedRegister(), registers->getRetrievalRegister());
10✔
345
        }
346
    }
347
    popCalleeSavedRegisters();
550✔
348
    assembly << instructionSet->leave();
550✔
349
    assembly << instructionSet->ret();
550✔
350
}
550✔
351

352
void StackMachine::retrieveProcedureReturnValue(std::string returnSymbolName) {
982✔
353
    Value& returnSymbol = resolve(returnSymbolName);
982✔
354
    emitStore(registers->getRetrievalRegister(), returnSymbol);
982✔
355
}
982✔
356

357
void StackMachine::xorCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10✔
358
    Value& leftOperand = resolve(leftOperandName);
10✔
359
    Value& rightOperand = resolve(rightOperandName);
10✔
360
    Register& resultRegister = get64BitRegister();
10✔
361

362
    if (residesInMemory(leftOperand)) {
10✔
363
        emitLoad(leftOperand, resultRegister);
8✔
364
    } else {
365
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2✔
366
    }
367
    if (residesInMemory(rightOperand)) {
10✔
368
        assembly << instructionSet->xor_(memoryOperand(rightOperand), resultRegister);
10✔
369
    } else {
370
        assembly << instructionSet->xor_(rightOperand.getAssignedRegister(), resultRegister);
×
371
    }
372
    bindResult(resultRegister, resolve(resultName));
10✔
373
}
10✔
374

375
void StackMachine::orCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10✔
376
    Value& leftOperand = resolve(leftOperandName);
10✔
377
    Value& rightOperand = resolve(rightOperandName);
10✔
378
    Register& resultRegister = get64BitRegister();
10✔
379

380
    if (residesInMemory(leftOperand)) {
10✔
381
        emitLoad(leftOperand, resultRegister);
8✔
382
    } else {
383
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2✔
384
    }
385
    if (residesInMemory(rightOperand)) {
10✔
386
        assembly << instructionSet->or_(memoryOperand(rightOperand), resultRegister);
10✔
387
    } else {
388
        assembly << instructionSet->or_(rightOperand.getAssignedRegister(), resultRegister);
×
389
    }
390
    bindResult(resultRegister, resolve(resultName));
10✔
391
}
10✔
392

393
void StackMachine::andCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10✔
394
    Value& leftOperand = resolve(leftOperandName);
10✔
395
    Value& rightOperand = resolve(rightOperandName);
10✔
396
    Register& resultRegister = get64BitRegister();
10✔
397

398
    if (residesInMemory(leftOperand)) {
10✔
399
        emitLoad(leftOperand, resultRegister);
8✔
400
    } else {
401
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2✔
402
    }
403
    if (residesInMemory(rightOperand)) {
10✔
404
        assembly << instructionSet->and_(memoryOperand(rightOperand), resultRegister);
10✔
405
    } else {
406
        assembly << instructionSet->and_(rightOperand.getAssignedRegister(), resultRegister);
×
407
    }
408
    bindResult(resultRegister, resolve(resultName));
10✔
409
}
10✔
410

411
void StackMachine::add(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
112✔
412
    Value& leftOperand = resolve(leftOperandName);
112✔
413
    Value& rightOperand = resolve(rightOperandName);
112✔
414
    Register& resultRegister = get64BitRegister();
112✔
415

416
    if (residesInMemory(leftOperand)) {
112✔
417
        emitLoad(leftOperand, resultRegister);
82✔
418
    } else {
419
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
30✔
420
    }
421
    if (residesInMemory(rightOperand)) {
112✔
422
        assembly << instructionSet->add(memoryOperand(rightOperand), resultRegister);
94✔
423
    } else {
424
        assembly << instructionSet->add(rightOperand.getAssignedRegister(), resultRegister);
18✔
425
    }
426
    bindResult(resultRegister, resolve(resultName));
112✔
427
}
112✔
428

429
void StackMachine::sub(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
30✔
430
    Value& leftOperand = resolve(leftOperandName);
30✔
431
    Value& rightOperand = resolve(rightOperandName);
30✔
432
    Register& resultRegister = get64BitRegister();
30✔
433

434
    if (residesInMemory(leftOperand)) {
30✔
435
        emitLoad(leftOperand, resultRegister);
24✔
436
    } else {
437
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
6✔
438
    }
439
    if (residesInMemory(rightOperand)) {
30✔
440
        assembly << instructionSet->sub(memoryOperand(rightOperand), resultRegister);
26✔
441
    } else {
442
        assembly << instructionSet->sub(rightOperand.getAssignedRegister(), resultRegister);
4✔
443
    }
444
    bindResult(resultRegister, resolve(resultName));
30✔
445
}
30✔
446

447
void StackMachine::mul(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
16✔
448
    Value& leftOperand = resolve(leftOperandName);
16✔
449
    Value& rightOperand = resolve(rightOperandName);
16✔
450
    Value& result = resolve(resultName);
16✔
451

452
    if (result.getType() != Type::INTEGRAL) {
16✔
453
        throw std::runtime_error{"multiplication of non integers is not implemented"};
×
454
    }
455

456
    Register& multiplicationRegister = registers->getMultiplicationRegister();
16✔
457
    assignRegisterToSymbol(multiplicationRegister, leftOperand);
16✔
458
    // imul writes RDX:RAX; spill RDX if it holds a live value (e.g. pointer for *p *= ...)
459
    storeRegisterValue(registers->getRemainderRegister());
16✔
460
    if (residesInMemory(rightOperand)) {
16✔
461
        assembly << instructionSet->imul(memoryOperand(rightOperand));
16✔
462
    } else {
463
        assembly << instructionSet->imul(rightOperand.getAssignedRegister());
×
464
    }
465
    bindResult(multiplicationRegister, result);
16✔
466
}
16✔
467

468
void StackMachine::div(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10✔
469
    Value& leftOperand = resolve(leftOperandName);
10✔
470
    Value& rightOperand = resolve(rightOperandName);
10✔
471
    Value& result = resolve(resultName);
10✔
472

473
    if (result.getType() != Type::INTEGRAL) {
10✔
474
        throw std::runtime_error{"division of non integer types is not implemented"};
×
475
    }
476

477
    Register& multiplicationRegister = registers->getMultiplicationRegister();
10✔
478
    assignRegisterToSymbol(multiplicationRegister, leftOperand);
10✔
479
    storeRegisterValue(registers->getRemainderRegister());
10✔
480
    assembly << instructionSet->xor_(registers->getRemainderRegister(), registers->getRemainderRegister());
10✔
481
    if (residesInMemory(rightOperand)) {
10✔
482
        assembly << instructionSet->idiv(memoryOperand(rightOperand));
10✔
483
    } else {
484
        assembly << instructionSet->idiv(rightOperand.getAssignedRegister());
×
485
    }
486
    bindResult(multiplicationRegister, result);
10✔
487
}
10✔
488

489
void StackMachine::mod(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10✔
490
    Value& leftOperand = resolve(leftOperandName);
10✔
491
    Value& rightOperand = resolve(rightOperandName);
10✔
492
    Value& result = resolve(resultName);
10✔
493

494
    if (result.getType() != Type::INTEGRAL) {
10✔
495
        throw std::runtime_error{"modular division of non integer types is not implemented"};
×
496
    }
497

498
    Register& multiplicationRegister = registers->getMultiplicationRegister();
10✔
499
    assignRegisterToSymbol(multiplicationRegister, leftOperand);
10✔
500
    storeRegisterValue(registers->getRemainderRegister());
10✔
501
    assembly << instructionSet->xor_(registers->getRemainderRegister(), registers->getRemainderRegister());
10✔
502
    if (residesInMemory(rightOperand)) {
10✔
503
        assembly << instructionSet->idiv(memoryOperand(rightOperand));
10✔
504
    } else {
505
        assembly << instructionSet->idiv(rightOperand.getAssignedRegister());
×
506
    }
507
    bindResult(registers->getRemainderRegister(), result);
10✔
508
}
10✔
509

510
void StackMachine::inc(std::string operandName) {
46✔
511
    Value& operand = resolve(operandName);
46✔
512
    if (residesInMemory(operand)) {
46✔
513
        assembly << instructionSet->inc(memoryOperand(operand));
18✔
514
    } else {
515
        assembly << instructionSet->inc(operand.getAssignedRegister());
28✔
516
    }
517
}
46✔
518

519
void StackMachine::dec(std::string operandName) {
12✔
520
    Value& operand = resolve(operandName);
12✔
521
    if (residesInMemory(operand)) {
12✔
522
        assembly << instructionSet->dec(memoryOperand(operand));
4✔
523
    } else {
524
        assembly << instructionSet->dec(operand.getAssignedRegister());
8✔
525
    }
526
}
12✔
527

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

544
    Value& leftOperand = resolve(leftOperandName);
48✔
545
    Register& resultRegister = get64BitRegisterExcluding(counterRegister);
48✔
546
    assignRegisterToSymbol(resultRegister, leftOperand);
48✔
547
    assembly << (instructionSet.get()->*emitShift)(resultRegister);
48✔
548
    Value& result = resolve(resultName);
48✔
549
    bindResult(resultRegister, result);
48✔
550
}
48✔
551

552
void StackMachine::shl(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
20✔
553
    shiftBy(leftOperandName, rightOperandName, resultName, &InstructionSet::shl);
20✔
554
}
20✔
555

556
void StackMachine::shr(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
28✔
557
    shiftBy(leftOperandName, rightOperandName, resultName, &InstructionSet::shr);
28✔
558
}
28✔
559

560
void StackMachine::storeRegisterValue(Register& reg) {
30,912✔
561
    if (reg.containsUnstoredValue()) {
30,912✔
562
        emitStore(reg, *reg.getValue());
1,098✔
563
        reg.free();
1,098✔
564
    }
565
}
30,912✔
566

567
void StackMachine::emptyGeneralPurposeRegisters() {
1,050✔
568
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
15,750✔
569
        reg->free();
14,700✔
570
    }
1,050✔
571
}
1,050✔
572

573
void StackMachine::pushCalleeSavedRegisters() { pushRegisters(registers->getCalleeSavedRegisters(), calleeSavedRegisters); }
530✔
574

575
void StackMachine::popCalleeSavedRegisters() { popRegisters(calleeSavedRegisters); }
550✔
576

577
void StackMachine::pushRegisters(std::vector<Register*> source, std::vector<Register*>& destination) {
530✔
578
    for (auto& reg : source) {
3,180✔
579
        pushRegister(*reg, destination);
2,650✔
580
    }
581
}
530✔
582

583
void StackMachine::popRegisters(std::vector<Register*> registers) {
550✔
584
    for (auto& reg : registers) {
3,290✔
585
        assembly << instructionSet->pop(*reg);
2,740✔
586
    }
587
}
550✔
588

589
void StackMachine::pushRegister(Register& reg, std::vector<Register*>& registers) {
2,650✔
590
    registers.insert(registers.begin(), &reg);
2,650✔
591
    assembly << instructionSet->push(reg);
2,650✔
592
}
2,650✔
593

594
void StackMachine::storeInMemory(Value& symbol) {
328✔
595
    if (!symbol.isStored()) {
328✔
596
        storeRegisterValue(symbol.getAssignedRegister());
×
597
    }
598
}
328✔
599

600
Address StackMachine::spillSlotAddress(const Value& symbol) const {
5,018✔
601
    int offset = symbol.getIndex() * MACHINE_WORD_SIZE
5,018✔
602
            + static_cast<int>(calleeSavedRegisters.size()) * MACHINE_WORD_SIZE;
5,018✔
603
    return Address::frame(FrameBase::StackPointer, offset, symbol.getSizeInBytes());
5,018✔
604
}
605

606
void StackMachine::registerFrameHome(const std::string& name, Address address) {
5,048✔
607
    auto inserted = frameHomes.emplace(name, std::move(address)).second;
5,048✔
608
    assert(inserted && "duplicate frame home registration");
5,048✔
609
}
5,048✔
610

611
bool StackMachine::residesInMemory(const Value& symbol) const {
7,978✔
612
    return addressOf(symbol).isGlobal() || symbol.isStored();
7,978✔
613
}
614

615
Address StackMachine::addressOf(const Value& symbol) const {
18,308✔
616
    const std::string& name = symbol.getName();
18,308✔
617
    auto frame = frameHomes.find(name);
18,308✔
618
    if (frame != frameHomes.end()) {
18,308✔
619
        return frame->second;
17,714✔
620
    }
621
    auto global = globalHomes.find(name);
594✔
622
    if (global != globalHomes.end()) {
594✔
623
        return global->second;
576✔
624
    }
625
    // No registered home: a temporary. Its spill slot is derived from Value::index, which the
626
    // code generator must keep consistent with the value's position among the frame's locals.
627
    return spillSlotAddress(symbol);
18✔
628
}
18,308✔
629

630
MemoryOperand StackMachine::memoryOperand(const Address& address) const {
9,280✔
631
    if (address.isGlobal()) {
9,280✔
632
        return MemoryOperand::global(address.label());
284✔
633
    }
634
    const Register& base = address.frameBase() == FrameBase::BasePointer ?
8,996✔
635
            registers->getBasePointer() : registers->getStackPointer();
8,996✔
636
    return MemoryOperand::at(base, address.offsetBytes());
8,996✔
637
}
638

639
MemoryOperand StackMachine::memoryOperand(const Value& symbol) const {
9,218✔
640
    return memoryOperand(addressOf(symbol));
9,218✔
641
}
642

643
Register& StackMachine::get64BitRegister() {
1,206✔
644
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
1,562✔
645
        if (!reg->containsUnstoredValue()) {
1,562✔
646
            return *reg;
1,206✔
647
        }
648
    }
1,206✔
649
    Register& reg = **registers->getGeneralPurposeRegisters().begin();
×
650
    storeRegisterValue(reg);
×
651
    return reg;
×
652
}
653

654
Register& StackMachine::get64BitRegisterExcluding(Register& registerToExclude) {
246✔
655
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
856✔
656
        if (reg != &registerToExclude && !reg->containsUnstoredValue()) {
852✔
657
            return *reg;
242✔
658
        }
659
    }
246✔
660
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
4✔
661
        if (reg != &registerToExclude) {
4✔
662
            storeRegisterValue(*reg);
4✔
663
            return *reg;
4✔
664
        }
665
    }
4✔
666
    throw std::runtime_error{"unable to get a free register"};
×
667
}
668

669
Register& StackMachine::getCounterRegister() {
48✔
670
    Register& counter = registers->getCounterRegister();
48✔
671
    storeRegisterValue(counter);
48✔
672
    return counter;
48✔
673
}
674

675
Register& StackMachine::assignRegisterTo(Value& symbol) {
208✔
676
    Register& reg = get64BitRegister();
208✔
677
    loadWithoutBinding(symbol, reg);
208✔
678
    // Bind only non-global homes; globals stay Address-only (scratch in reg).
679
    if (!addressOf(symbol).isGlobal()) {
208✔
680
        reg.assign(&symbol);
198✔
681
    }
682
    return reg;
208✔
683
}
684

685
Register& StackMachine::assignRegisterExcluding(Value& symbol, Register& registerToExclude) {
6✔
686
    Register& reg = get64BitRegisterExcluding(registerToExclude);
6✔
687
    loadWithoutBinding(symbol, reg);
6✔
688
    if (!addressOf(symbol).isGlobal()) {
6✔
689
        reg.assign(&symbol);
6✔
690
    }
691
    return reg;
6✔
692
}
693

694
void StackMachine::setScope(std::vector<Value> variables) {
16✔
695
    for (auto& var : variables) {
64✔
696
        scopeValues.insert({var.getName(), var});
48✔
697
        registerFrameHome(var.getName(), spillSlotAddress(var));
48✔
698
    }
699
}
16✔
700

701
Value& StackMachine::resolve(const std::string& name) {
10,102✔
702
    auto local = scopeValues.find(name);
10,102✔
703
    if (local != scopeValues.end()) {
10,102✔
704
        return local->second;
9,818✔
705
    }
706
    auto global = globals.find(name);
284✔
707
    if (global != globals.end()) {
284✔
708
        return global->second;
284✔
709
    }
NEW
710
    throw std::runtime_error { "codegen: no storage for symbol `" + name + "` (function designator or missing global?)" };
×
711
}
712

713
} // namespace codegen
714

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