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

rieske / trans / 28461496314

30 Jun 2026 04:55PM UTC coverage: 90.13% (-0.4%) from 90.53%
28461496314

Pull #44

github

rieske
Support multi-word struct pass/return and expand struct tests

Pass multi-word structs on the stack with correct SP adjustments when
pushing words; copy aggregates word-wise on assign. Return up to two
words in RAX/RDX and restore them on retrieve. Use the real function
return type from declaration specs (fixes void calls and struct
returns). Expand StructsTest for by-value/by-address args in different
positions, mutation, return by value/pointer, assign, and chains.
Pull Request #44: Implement C structs with member access

329 of 404 new or added lines in 13 files covered. (81.44%)

5415 of 6008 relevant lines covered (90.13%)

240586.41 hits per line

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

94.17
/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) :
430 ✔
17
        assembly{ostream},
430 ✔
18
        instructionSet{std::move(instructionSet)},
430 ✔
19
        registers{std::move(registers)} {}
860 ✔
20

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

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

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

39
    for (auto& value : values) {
5,470 ✔
40
        scopeValues.insert({value.getName(), value});
4,954 ✔
41
    }
42
    std::size_t integerArgumentRegisterIndex{0};
516 ✔
43
    std::size_t localIndex{scopeValues.size()};
516 ✔
44
    int argumentWordIndex{0};
516 ✔
45
    for (auto& argument : arguments) {
710 ✔
46
        const int words = std::max(1, (argument.getSizeInBytes() + MACHINE_WORD_SIZE - 1) / MACHINE_WORD_SIZE);
194 ✔
47
        // Single-word args may use integer registers; multi-word (structs) always use the stack.
48
        if (words == 1 && integerArgumentRegisterIndex < registers->getIntegerArgumentRegisters().size()) {
194 ✔
49
            Value registerArgument{argument.getName(), static_cast<int>(localIndex), argument.getType(), argument.getSizeInBytes()};
136 ✔
50
            scopeValues.insert({argument.getName(), registerArgument});
136 ✔
51
            registers->getIntegerArgumentRegisters()[integerArgumentRegisterIndex]->assign(&resolve(argument.getName()));
136 ✔
52
            ++integerArgumentRegisterIndex;
136 ✔
53
            ++localIndex;
136 ✔
54
        } else {
136 ✔
55
            Value stackArgument{argument.getName(), argumentWordIndex, argument.getType(), argument.getSizeInBytes()};
58 ✔
56
            scopeValues.insert({argument.getName(), stackArgument});
58 ✔
57
            registerFrameHome(argument.getName(), Address::frame(FrameBase::BasePointer,
116 ✔
58
                    (argumentWordIndex + 2) * MACHINE_WORD_SIZE, argument.getSizeInBytes()));
58 ✔
59
            argumentWordIndex += words;
58 ✔
60
        }
58 ✔
61
    }
62
    int savedRegistersStack = registers->getCalleeSavedRegisters().size() * MACHINE_WORD_SIZE;
516 ✔
63
    // SP locals use word index; size may span multiple words (structs).
64
    int maxWordEnd = 0;
516 ✔
65
    for (const auto& entry : scopeValues) {
5,664 ✔
66
        if (frameHomes.count(entry.first)) {
5,148 ✔
67
            continue; // stack argument on RBP
58 ✔
68
        }
69
        int words = (entry.second.getSizeInBytes() + MACHINE_WORD_SIZE - 1) / MACHINE_WORD_SIZE;
5,090 ✔
70
        if (words < 1) {
5,090 ✔
NEW
71
            words = 1;
×
72
        }
73
        maxWordEnd = std::max(maxWordEnd, entry.second.getIndex() + words);
5,090 ✔
74
    }
75
    localVariableStackSize = maxWordEnd * MACHINE_WORD_SIZE;
516 ✔
76
    int stackSize = savedRegistersStack + localVariableStackSize;
516 ✔
77
    if (stackSize % STACK_ALIGNMENT) {
516 ✔
78
        assembly << instructionSet->sub(registers->getStackPointer(), localVariableStackSize + MACHINE_WORD_SIZE);
220 ✔
79
    } else {
80
        assembly << instructionSet->sub(registers->getStackPointer(), localVariableStackSize);
296 ✔
81
    }
82

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

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

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

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

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

138
void StackMachine::spillCallerSavedRegisters() {
964 ✔
139
    for (auto& reg : registers->getCallerSavedRegisters()) {
8,676 ✔
140
        storeRegisterValue(*reg);
7,712 ✔
141
    }
964 ✔
142
}
964 ✔
143

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

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

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

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

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

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

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

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

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

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

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

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

244
void StackMachine::assign(std::string operandName, std::string resultName) {
434 ✔
245
    auto& operand = resolve(operandName);
434 ✔
246
    auto& result = resolve(resultName);
434 ✔
247
    if (wordCount(operand) > 1 || wordCount(result) > 1) {
434 ✔
248
        copyWords(operand, result);
6 ✔
249
        return;
6 ✔
250
    }
251
    if (residesInMemory(operand) && residesInMemory(result)) {
428 ✔
252
        Register& reg = get64BitRegister();
292 ✔
253
        emitLoad(operand, reg);
292 ✔
254
        emitStore(reg, result);
292 ✔
255
    } else if (residesInMemory(operand)) {
136 ✔
256
        emitLoad(operand, result.getAssignedRegister());
×
257
    } else if (residesInMemory(result)) {
136 ✔
258
        emitStore(operand.getAssignedRegister(), result);
136 ✔
259
    } else {
260
        assembly << instructionSet->mov(operand.getAssignedRegister(), result.getAssignedRegister());
×
261
    }
262
}
263

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

273
void StackMachine::lvalueAssign(std::string operandName, std::string resultName) {
156 ✔
274
    auto& operand = resolve(operandName);
156 ✔
275
    auto& result = resolve(resultName);
156 ✔
276

277
    Register& operandRegister = residesInMemory(operand) ? assignRegisterTo(operand) : operand.getAssignedRegister();
156 ✔
278
    Register& resultRegister = residesInMemory(result) ? assignRegisterExcluding(result, operandRegister) : result.getAssignedRegister();
156 ✔
279
    assembly << instructionSet->mov(operandRegister, MemoryOperand::at(resultRegister, 0));
156 ✔
280
}
156 ✔
281

282
void StackMachine::procedureArgument(std::string argumentName) {
2,088 ✔
283
    auto argument = &resolve(argumentName);
2,088 ✔
284
    // Multi-word values (structs) always travel on the stack.
285
    if (wordCount(*argument) == 1 && integerArguments.size() < registers->getIntegerArgumentRegisters().size()) {
2,088 ✔
286
        integerArguments.push_back(argument);
2,016 ✔
287
    } else {
288
        stackArguments.insert(stackArguments.begin(), argument);
72 ✔
289
    }
290
}
2,088 ✔
291

292
void StackMachine::callProcedure(std::string procedureName) {
964 ✔
293
    for (std::size_t i = 0; i < integerArguments.size(); ++i) {
2,980 ✔
294
        assignRegisterToSymbol(*registers->getIntegerArgumentRegisters()[i], *integerArguments[i]);
2,016 ✔
295
    }
296
    storeRegisterValue(registers->getRetrievalRegister());
964 ✔
297
    spillCallerSavedRegisters();
964 ✔
298
    int argumentOffset{0};
964 ✔
299
    int stackWords = 0;
964 ✔
300
    for (auto argument : stackArguments) {
1,036 ✔
301
        stackWords += wordCount(*argument);
72 ✔
302
    }
303
    // System V AMD64: RSP must be 16-byte aligned before call.
304
    if (stackWords % 2 == 1) {
964 ✔
305
        assembly << instructionSet->sub(registers->getStackPointer(), MACHINE_WORD_SIZE);
12 ✔
306
        argumentOffset += MACHINE_WORD_SIZE;
12 ✔
307
    }
308
    for (auto argument : stackArguments) {
1,036 ✔
309
        argumentOffset += pushProcedureArgument(*argument, argumentOffset);
72 ✔
310
    }
311
    integerArguments.clear();
964 ✔
312
    stackArguments.clear();
964 ✔
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();
964 ✔
316
    assembly << instructionSet->xor_(retrievalRegister, retrievalRegister);
964 ✔
317
    assembly << instructionSet->call(procedureName);
964 ✔
318
    if (argumentOffset) {
964 ✔
319
        assembly << instructionSet->add(registers->getStackPointer(), argumentOffset);
26 ✔
320
    }
321
}
964 ✔
322

323
int StackMachine::pushProcedureArgument(Value& symbolToPush, int argumentOffset) {
72 ✔
324
    const int words = wordCount(symbolToPush);
72 ✔
325
    // Push high word first so the lowest address holds word 0 (matches callee RBP layout).
326
    for (int w = words - 1; w >= 0; --w) {
156 ✔
327
        Register& reg = get64BitRegister();
84 ✔
328
        if (residesInMemory(symbolToPush) || words > 1) {
84 ✔
329
            Address home = addressOf(symbolToPush);
84 ✔
330
            int byteOff = w * MACHINE_WORD_SIZE;
84 ✔
331
            if (!home.isGlobal() && home.frameBase() == FrameBase::StackPointer) {
84 ✔
332
                // Prior pushes in this argument (and earlier args) have moved RSP.
333
                const int pushedInThisArg = (words - 1 - w) * MACHINE_WORD_SIZE;
84 ✔
334
                byteOff += argumentOffset + pushedInThisArg;
84 ✔
335
            }
336
            if (home.isGlobal()) {
84 ✔
NEW
337
                Register& addr = get64BitRegisterExcluding(reg);
×
NEW
338
                assembly << instructionSet->lea(memoryOperand(home), addr);
×
NEW
339
                if (byteOff) {
×
NEW
340
                    assembly << instructionSet->add(addr, byteOff);
×
341
                }
NEW
342
                assembly << instructionSet->mov(MemoryOperand::at(addr, 0), reg);
×
343
            } else {
344
                Address wordHome = Address::frame(home.frameBase(), home.offsetBytes() + byteOff, MACHINE_WORD_SIZE);
84 ✔
345
                assembly << instructionSet->mov(memoryOperand(wordHome), reg);
84 ✔
346
            }
84 ✔
347
            assembly << instructionSet->push(reg);
84 ✔
348
        } else {
84 ✔
NEW
349
            assembly << instructionSet->push(symbolToPush.getAssignedRegister());
×
350
        }
351
    }
352
    return words * MACHINE_WORD_SIZE;
72 ✔
353
}
354

355
void StackMachine::returnFromProcedure(std::string returnSymbolName) {
532 ✔
356
    if (!returnSymbolName.empty()) {
532 ✔
357
        Value& returnSymbol = resolve(returnSymbolName);
484 ✔
358
        const int words = wordCount(returnSymbol);
484 ✔
359
        // Up to two words returned in RAX and RDX (enough for our word-aligned Point, etc.).
360
        loadWord(returnSymbol, 0, registers->getRetrievalRegister());
484 ✔
361
        if (words >= 2) {
484 ✔
362
            loadWord(returnSymbol, 1, registers->getRemainderRegister());
2 ✔
363
        }
364
    }
365
    popCalleeSavedRegisters();
532 ✔
366
    assembly << instructionSet->leave();
532 ✔
367
    assembly << instructionSet->ret();
532 ✔
368
}
532 ✔
369

370
void StackMachine::retrieveProcedureReturnValue(std::string returnSymbolName) {
914 ✔
371
    Value& returnSymbol = resolve(returnSymbolName);
914 ✔
372
    const int words = wordCount(returnSymbol);
914 ✔
373
    storeWord(registers->getRetrievalRegister(), returnSymbol, 0);
914 ✔
374
    if (words >= 2) {
914 ✔
375
        storeWord(registers->getRemainderRegister(), returnSymbol, 1);
4 ✔
376
    }
377
}
914 ✔
378

379
void StackMachine::xorCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
380
    Value& leftOperand = resolve(leftOperandName);
10 ✔
381
    Value& rightOperand = resolve(rightOperandName);
10 ✔
382
    Register& resultRegister = get64BitRegister();
10 ✔
383

384
    if (residesInMemory(leftOperand)) {
10 ✔
385
        emitLoad(leftOperand, resultRegister);
8 ✔
386
    } else {
387
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2 ✔
388
    }
389
    if (residesInMemory(rightOperand)) {
10 ✔
390
        assembly << instructionSet->xor_(memoryOperand(rightOperand), resultRegister);
10 ✔
391
    } else {
392
        assembly << instructionSet->xor_(rightOperand.getAssignedRegister(), resultRegister);
×
393
    }
394
    bindResult(resultRegister, resolve(resultName));
10 ✔
395
}
10 ✔
396

397
void StackMachine::orCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
398
    Value& leftOperand = resolve(leftOperandName);
10 ✔
399
    Value& rightOperand = resolve(rightOperandName);
10 ✔
400
    Register& resultRegister = get64BitRegister();
10 ✔
401

402
    if (residesInMemory(leftOperand)) {
10 ✔
403
        emitLoad(leftOperand, resultRegister);
8 ✔
404
    } else {
405
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2 ✔
406
    }
407
    if (residesInMemory(rightOperand)) {
10 ✔
408
        assembly << instructionSet->or_(memoryOperand(rightOperand), resultRegister);
10 ✔
409
    } else {
410
        assembly << instructionSet->or_(rightOperand.getAssignedRegister(), resultRegister);
×
411
    }
412
    bindResult(resultRegister, resolve(resultName));
10 ✔
413
}
10 ✔
414

415
void StackMachine::andCommand(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
416
    Value& leftOperand = resolve(leftOperandName);
10 ✔
417
    Value& rightOperand = resolve(rightOperandName);
10 ✔
418
    Register& resultRegister = get64BitRegister();
10 ✔
419

420
    if (residesInMemory(leftOperand)) {
10 ✔
421
        emitLoad(leftOperand, resultRegister);
8 ✔
422
    } else {
423
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
2 ✔
424
    }
425
    if (residesInMemory(rightOperand)) {
10 ✔
426
        assembly << instructionSet->and_(memoryOperand(rightOperand), resultRegister);
10 ✔
427
    } else {
428
        assembly << instructionSet->and_(rightOperand.getAssignedRegister(), resultRegister);
×
429
    }
430
    bindResult(resultRegister, resolve(resultName));
10 ✔
431
}
10 ✔
432

433
void StackMachine::add(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
118 ✔
434
    Value& leftOperand = resolve(leftOperandName);
118 ✔
435
    Value& rightOperand = resolve(rightOperandName);
118 ✔
436
    Register& resultRegister = get64BitRegister();
118 ✔
437

438
    if (residesInMemory(leftOperand)) {
118 ✔
439
        emitLoad(leftOperand, resultRegister);
66 ✔
440
    } else {
441
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
52 ✔
442
    }
443
    if (residesInMemory(rightOperand)) {
118 ✔
444
        assembly << instructionSet->add(memoryOperand(rightOperand), resultRegister);
76 ✔
445
    } else {
446
        assembly << instructionSet->add(rightOperand.getAssignedRegister(), resultRegister);
42 ✔
447
    }
448
    bindResult(resultRegister, resolve(resultName));
118 ✔
449
}
118 ✔
450

451
void StackMachine::sub(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
30 ✔
452
    Value& leftOperand = resolve(leftOperandName);
30 ✔
453
    Value& rightOperand = resolve(rightOperandName);
30 ✔
454
    Register& resultRegister = get64BitRegister();
30 ✔
455

456
    if (residesInMemory(leftOperand)) {
30 ✔
457
        emitLoad(leftOperand, resultRegister);
22 ✔
458
    } else {
459
        assembly << instructionSet->mov(leftOperand.getAssignedRegister(), resultRegister);
8 ✔
460
    }
461
    if (residesInMemory(rightOperand)) {
30 ✔
462
        assembly << instructionSet->sub(memoryOperand(rightOperand), resultRegister);
26 ✔
463
    } else {
464
        assembly << instructionSet->sub(rightOperand.getAssignedRegister(), resultRegister);
4 ✔
465
    }
466
    bindResult(resultRegister, resolve(resultName));
30 ✔
467
}
30 ✔
468

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

474
    if (result.getType() != Type::INTEGRAL) {
16 ✔
475
        throw std::runtime_error{"multiplication of non integers is not implemented"};
×
476
    }
477

478
    Register& multiplicationRegister = registers->getMultiplicationRegister();
16 ✔
479
    assignRegisterToSymbol(multiplicationRegister, leftOperand);
16 ✔
480
    // imul writes RDX:RAX; spill RDX if it holds a live value (e.g. pointer for *p *= ...)
481
    storeRegisterValue(registers->getRemainderRegister());
16 ✔
482
    if (residesInMemory(rightOperand)) {
16 ✔
483
        assembly << instructionSet->imul(memoryOperand(rightOperand));
16 ✔
484
    } else {
485
        assembly << instructionSet->imul(rightOperand.getAssignedRegister());
×
486
    }
487
    bindResult(multiplicationRegister, result);
16 ✔
488
}
16 ✔
489

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

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

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

511
void StackMachine::mod(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
10 ✔
512
    Value& leftOperand = resolve(leftOperandName);
10 ✔
513
    Value& rightOperand = resolve(rightOperandName);
10 ✔
514
    Value& result = resolve(resultName);
10 ✔
515

516
    if (result.getType() != Type::INTEGRAL) {
10 ✔
517
        throw std::runtime_error{"modular division of non integer types is not implemented"};
×
518
    }
519

520
    Register& multiplicationRegister = registers->getMultiplicationRegister();
10 ✔
521
    assignRegisterToSymbol(multiplicationRegister, leftOperand);
10 ✔
522
    storeRegisterValue(registers->getRemainderRegister());
10 ✔
523
    assembly << instructionSet->xor_(registers->getRemainderRegister(), registers->getRemainderRegister());
10 ✔
524
    if (residesInMemory(rightOperand)) {
10 ✔
525
        assembly << instructionSet->idiv(memoryOperand(rightOperand));
10 ✔
526
    } else {
527
        assembly << instructionSet->idiv(rightOperand.getAssignedRegister());
×
528
    }
529
    bindResult(registers->getRemainderRegister(), result);
10 ✔
530
}
10 ✔
531

532
void StackMachine::inc(std::string operandName) {
40 ✔
533
    Value& operand = resolve(operandName);
40 ✔
534
    if (residesInMemory(operand)) {
40 ✔
535
        assembly << instructionSet->inc(memoryOperand(operand));
12 ✔
536
    } else {
537
        assembly << instructionSet->inc(operand.getAssignedRegister());
28 ✔
538
    }
539
}
40 ✔
540

541
void StackMachine::dec(std::string operandName) {
12 ✔
542
    Value& operand = resolve(operandName);
12 ✔
543
    if (residesInMemory(operand)) {
12 ✔
544
        assembly << instructionSet->dec(memoryOperand(operand));
4 ✔
545
    } else {
546
        assembly << instructionSet->dec(operand.getAssignedRegister());
8 ✔
547
    }
548
}
12 ✔
549

550
void StackMachine::shiftBy(std::string leftOperandName, std::string rightOperandName, std::string resultName,
38 ✔
551
        std::string (InstructionSet::*emitShift)(const Register&) const) {
552
    // Count must live in %cl (RCX) and be tracked so the value is not placed in RCX.
553
    Register& counterRegister = getCounterRegister();
38 ✔
554
    Value& rightOperand = resolve(rightOperandName);
38 ✔
555
    if (residesInMemory(rightOperand)) {
38 ✔
556
        emitLoad(rightOperand, counterRegister);
38 ✔
557
    } else if (&counterRegister != &rightOperand.getAssignedRegister()) {
×
558
        assembly << instructionSet->mov(rightOperand.getAssignedRegister(), counterRegister);
×
559
        storeRegisterValue(rightOperand.getAssignedRegister());
×
560
    }
561
    // Count in %cl is scratch only; never register-cache a global home on RCX.
562
    if (!addressOf(rightOperand).isGlobal()) {
38 ✔
563
        counterRegister.assign(&rightOperand);
32 ✔
564
    }
565

566
    Value& leftOperand = resolve(leftOperandName);
38 ✔
567
    Register& resultRegister = get64BitRegisterExcluding(counterRegister);
38 ✔
568
    assignRegisterToSymbol(resultRegister, leftOperand);
38 ✔
569
    assembly << (instructionSet.get()->*emitShift)(resultRegister);
38 ✔
570
    Value& result = resolve(resultName);
38 ✔
571
    bindResult(resultRegister, result);
38 ✔
572
}
38 ✔
573

574
void StackMachine::shl(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
20 ✔
575
    shiftBy(leftOperandName, rightOperandName, resultName, &InstructionSet::shl);
20 ✔
576
}
20 ✔
577

578
void StackMachine::shr(std::string leftOperandName, std::string rightOperandName, std::string resultName) {
18 ✔
579
    shiftBy(leftOperandName, rightOperandName, resultName, &InstructionSet::shr);
18 ✔
580
}
18 ✔
581

582
void StackMachine::storeRegisterValue(Register& reg) {
23,338 ✔
583
    if (reg.containsUnstoredValue()) {
23,338 ✔
584
        emitStore(reg, *reg.getValue());
1,382 ✔
585
        reg.free();
1,382 ✔
586
    }
587
}
23,338 ✔
588

589
void StackMachine::emptyGeneralPurposeRegisters() {
1,022 ✔
590
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
15,330 ✔
591
        reg->free();
14,308 ✔
592
    }
1,022 ✔
593
}
1,022 ✔
594

595
void StackMachine::pushCalleeSavedRegisters() { pushRegisters(registers->getCalleeSavedRegisters(), calleeSavedRegisters); }
516 ✔
596

597
void StackMachine::popCalleeSavedRegisters() { popRegisters(calleeSavedRegisters); }
532 ✔
598

599
void StackMachine::pushRegisters(std::vector<Register*> source, std::vector<Register*>& destination) {
516 ✔
600
    for (auto& reg : source) {
3,096 ✔
601
        pushRegister(*reg, destination);
2,580 ✔
602
    }
603
}
516 ✔
604

605
void StackMachine::popRegisters(std::vector<Register*> registers) {
532 ✔
606
    for (auto& reg : registers) {
3,182 ✔
607
        assembly << instructionSet->pop(*reg);
2,650 ✔
608
    }
609
}
532 ✔
610

611
void StackMachine::pushRegister(Register& reg, std::vector<Register*>& registers) {
2,580 ✔
612
    registers.insert(registers.begin(), &reg);
2,580 ✔
613
    assembly << instructionSet->push(reg);
2,580 ✔
614
}
2,580 ✔
615

616
void StackMachine::storeInMemory(Value& symbol) {
346 ✔
617
    if (!symbol.isStored()) {
346 ✔
618
        storeRegisterValue(symbol.getAssignedRegister());
×
619
    }
620
}
346 ✔
621

622
Address StackMachine::spillSlotAddress(const Value& symbol) const {
5,156 ✔
623
    int offset = symbol.getIndex() * MACHINE_WORD_SIZE
5,156 ✔
624
            + static_cast<int>(calleeSavedRegisters.size()) * MACHINE_WORD_SIZE;
5,156 ✔
625
    return Address::frame(FrameBase::StackPointer, offset, symbol.getSizeInBytes());
5,156 ✔
626
}
627

628
void StackMachine::registerFrameHome(const std::string& name, Address address) {
5,196 ✔
629
    auto inserted = frameHomes.emplace(name, std::move(address)).second;
5,196 ✔
630
    assert(inserted && "duplicate frame home registration");
5,196 ✔
631
}
5,196 ✔
632

633
bool StackMachine::residesInMemory(const Value& symbol) const {
8,850 ✔
634
    return addressOf(symbol).isGlobal() || symbol.isStored();
8,850 ✔
635
}
636

637
Address StackMachine::addressOf(const Value& symbol) const {
19,638 ✔
638
    const std::string& name = symbol.getName();
19,638 ✔
639
    auto frame = frameHomes.find(name);
19,638 ✔
640
    if (frame != frameHomes.end()) {
19,638 ✔
641
        return frame->second;
19,030 ✔
642
    }
643
    auto global = globalHomes.find(name);
608 ✔
644
    if (global != globalHomes.end()) {
608 ✔
645
        return global->second;
590 ✔
646
    }
647
    // No registered home: a temporary. Its spill slot is derived from Value::index, which the
648
    // code generator must keep consistent with the value's position among the frame's locals.
649
    return spillSlotAddress(symbol);
18 ✔
650
}
19,638 ✔
651

652
MemoryOperand StackMachine::memoryOperand(const Address& address) const {
9,350 ✔
653
    if (address.isGlobal()) {
9,350 ✔
654
        return MemoryOperand::global(address.label());
298 ✔
655
    }
656
    const Register& base = address.frameBase() == FrameBase::BasePointer ?
9,052 ✔
657
            registers->getBasePointer() : registers->getStackPointer();
9,052 ✔
658
    return MemoryOperand::at(base, address.offsetBytes());
9,052 ✔
659
}
660

661
MemoryOperand StackMachine::memoryOperand(const Value& symbol) const {
7,866 ✔
662
    return memoryOperand(addressOf(symbol));
7,866 ✔
663
}
664

665
Register& StackMachine::get64BitRegister() {
1,430 ✔
666
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
3,540 ✔
667
        if (!reg->containsUnstoredValue()) {
3,508 ✔
668
            return *reg;
1,398 ✔
669
        }
670
    }
1,430 ✔
671
    Register& reg = **registers->getGeneralPurposeRegisters().begin();
32 ✔
672
    storeRegisterValue(reg);
32 ✔
673
    return reg;
32 ✔
674
}
675

676
Register& StackMachine::get64BitRegisterExcluding(Register& registerToExclude) {
580 ✔
677
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
3,482 ✔
678
        if (reg != &registerToExclude && !reg->containsUnstoredValue()) {
3,422 ✔
679
            return *reg;
520 ✔
680
        }
681
    }
580 ✔
682
    for (auto& reg : registers->getGeneralPurposeRegisters()) {
104 ✔
683
        if (reg != &registerToExclude) {
104 ✔
684
            storeRegisterValue(*reg);
60 ✔
685
            return *reg;
60 ✔
686
        }
687
    }
60 ✔
688
    throw std::runtime_error{"unable to get a free register"};
×
689
}
690

691
Register& StackMachine::getCounterRegister() {
38 ✔
692
    Register& counter = registers->getCounterRegister();
38 ✔
693
    storeRegisterValue(counter);
38 ✔
694
    return counter;
38 ✔
695
}
696

697
Register& StackMachine::assignRegisterTo(Value& symbol) {
272 ✔
698
    Register& reg = get64BitRegister();
272 ✔
699
    loadWithoutBinding(symbol, reg);
272 ✔
700
    // Bind only non-global homes; globals stay Address-only (scratch in reg).
701
    if (!addressOf(symbol).isGlobal()) {
272 ✔
702
        reg.assign(&symbol);
262 ✔
703
    }
704
    return reg;
272 ✔
705
}
706

707
Register& StackMachine::assignRegisterExcluding(Value& symbol, Register& registerToExclude) {
12 ✔
708
    Register& reg = get64BitRegisterExcluding(registerToExclude);
12 ✔
709
    loadWithoutBinding(symbol, reg);
12 ✔
710
    if (!addressOf(symbol).isGlobal()) {
12 ✔
711
        reg.assign(&symbol);
12 ✔
712
    }
713
    return reg;
12 ✔
714
}
715

716
void StackMachine::setScope(std::vector<Value> variables) {
16 ✔
717
    for (auto& var : variables) {
64 ✔
718
        scopeValues.insert({var.getName(), var});
48 ✔
719
        registerFrameHome(var.getName(), spillSlotAddress(var));
48 ✔
720
    }
721
}
16 ✔
722

723
Value& StackMachine::resolve(const std::string& name) {
10,662 ✔
724
    auto local = scopeValues.find(name);
10,662 ✔
725
    if (local != scopeValues.end()) {
10,662 ✔
726
        return local->second;
10,364 ✔
727
    }
728
    return globals.at(name);
298 ✔
729
}
730

731

732

733

734

735
int StackMachine::wordCount(const Value& symbol) const {
5,914 ✔
736
    int words = (symbol.getSizeInBytes() + MACHINE_WORD_SIZE - 1) / MACHINE_WORD_SIZE;
5,914 ✔
737
    return words < 1 ? 1 : words;
5,914 ✔
738
}
739

740
void StackMachine::loadWord(Value& symbol, int wordIndex, Register& dest) {
498 ✔
741
    // Single-word values may be register-resident; multi-word objects always live in memory.
742
    if (wordIndex == 0 && wordCount(symbol) == 1 && !residesInMemory(symbol)) {
498 ✔
743
        if (&dest != &symbol.getAssignedRegister()) {
28 ✔
744
            assembly << instructionSet->mov(symbol.getAssignedRegister(), dest);
24 ✔
745
        }
746
        return;
28 ✔
747
    }
748
    Address home = addressOf(symbol);
470 ✔
749
    const int byteOff = wordIndex * MACHINE_WORD_SIZE;
470 ✔
750
    if (home.isGlobal()) {
470 ✔
751
        Register& addr = get64BitRegisterExcluding(dest);
4 ✔
752
        assembly << instructionSet->lea(memoryOperand(home), addr);
4 ✔
753
        if (byteOff) {
4 ✔
NEW
754
            assembly << instructionSet->add(addr, byteOff);
×
755
        }
756
        assembly << instructionSet->mov(MemoryOperand::at(addr, 0), dest);
4 ✔
757
    } else {
758
        Address wordHome = Address::frame(home.frameBase(), home.offsetBytes() + byteOff, MACHINE_WORD_SIZE);
466 ✔
759
        assembly << instructionSet->mov(memoryOperand(wordHome), dest);
466 ✔
760
    }
466 ✔
761
}
470 ✔
762

763
void StackMachine::storeWord(Register& source, Value& symbol, int wordIndex) {
930 ✔
764
    if (wordIndex == 0 && wordCount(symbol) == 1 && !residesInMemory(symbol)) {
930 ✔
NEW
765
        if (&source != &symbol.getAssignedRegister()) {
×
NEW
766
            assembly << instructionSet->mov(source, symbol.getAssignedRegister());
×
767
        }
NEW
768
        return;
×
769
    }
770
    Address home = addressOf(symbol);
930 ✔
771
    const int byteOff = wordIndex * MACHINE_WORD_SIZE;
930 ✔
772
    if (home.isGlobal()) {
930 ✔
NEW
773
        Register& addr = get64BitRegisterExcluding(source);
×
NEW
774
        assembly << instructionSet->lea(memoryOperand(home), addr);
×
NEW
775
        if (byteOff) {
×
NEW
776
            assembly << instructionSet->add(addr, byteOff);
×
777
        }
NEW
778
        assembly << instructionSet->mov(source, MemoryOperand::at(addr, 0));
×
779
    } else {
780
        Address wordHome = Address::frame(home.frameBase(), home.offsetBytes() + byteOff, MACHINE_WORD_SIZE);
930 ✔
781
        assembly << instructionSet->mov(source, memoryOperand(wordHome));
930 ✔
782
    }
930 ✔
783
}
930 ✔
784

785
void StackMachine::copyWords(Value& source, Value& destination) {
6 ✔
786
    const int words = std::max(wordCount(source), wordCount(destination));
6 ✔
787
    Register& reg = get64BitRegister();
6 ✔
788
    for (int w = 0; w < words; ++w) {
18 ✔
789
        loadWord(source, w, reg);
12 ✔
790
        storeWord(reg, destination, w);
12 ✔
791
    }
792
}
6 ✔
793

794
void StackMachine::fieldAddress(std::string baseName, int offsetBytes, std::string resultName, bool baseIsPointer) {
170 ✔
795
    auto& base = resolve(baseName);
170 ✔
796
    Register& addrReg = get64BitRegister();
170 ✔
797
    if (baseIsPointer) {
170 ✔
798
        // Arrow: base holds a pointer value (object address).
799
        if (residesInMemory(base)) {
28 ✔
800
            emitLoad(base, addrReg);
16 ✔
801
        } else {
802
            assembly << instructionSet->mov(base.getAssignedRegister(), addrReg);
12 ✔
803
        }
804
    } else {
805
        // Dot: base is the object; take its address.
806
        assembly << instructionSet->lea(memoryOperand(base), addrReg);
142 ✔
807
    }
808
    if (offsetBytes) {
170 ✔
809
        assembly << instructionSet->add(addrReg, offsetBytes);
88 ✔
810
    }
811
    bindResult(addrReg, resolve(resultName));
170 ✔
812
}
170 ✔
813

814
} // namespace codegen
815

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