Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Assembly (x86-64, AArch64, RISC-V) Module 11

Reading compiler output interview questions

Interview questions on reading compiler output: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.

19 questions: 5 warm-ups and 14 with model answers · Levels: Fresher to staff

Warm-up questions

Short questions with short answers, each answered in full.

  1. Fresher to mid

    Why do engineers usually read -O2 output rather than -O0 output when they check what a compiler did with their code?

    Answered in the lesson Reading compiler output: -O0 versus -O2

  2. Fresher to mid

    Does adding -g to a release build change the machine code or slow the program down?

    Answered in the lesson Debug info: symbols and source mapping

  3. Fresher to mid

    Why does a C++ compiler mangle names at all?

    Answered in the lesson C++ and Rust in assembly: names, vtables, checks

  4. Fresher to mid

    What does -O2 turn on that -O0 leaves off?

    Answered in the lesson Optimisation and undefined behaviour in assembly

  5. Fresher to mid

    Why does a compiler write xor eax, eax rather than mov eax, 0?

    Answered in the lesson Compiler idioms you will meet

Questions with model answers

What each question tests, the first step of a good answer and the follow-ups to expect.

  1. Mid to senior

    A code review states that a summing loop like sum() makes exactly one memory access per element in the release build, on x86-64 and on AArch64. How do you check the claim from compiler output, and what in the listings confirms or refutes it?

    What it tests
    Producing listings with the build's own flags, filtering them, finding the loop by its branch back, and counting memory operands on two ISAs, including x86-64 arithmetic that reads memory directly.
    First step
    Compile the file for each target with the release flags plus -S, filter out directives and comments but keep the labels, find the block that the backward branch returns to, and list every instruction in it that reads or writes memory.
    Follow-ups

    From the lesson Reading compiler output: -O0 versus -O2

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.

    Opening the model answers with your pass…

    Model answers open with any MySmartCoPilot pass.

    • Pro pass · 1 week₹79$2.99
    • 30 days₹179$5.99
    • 1 year₹1,699$45

    One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

    It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  2. Mid to staff

    One C function compiles to 10 instructions on one x86-64 Linux machine and to 17 on another, with the same GCC version at -O2. The extra ones are endbr64, two reads of fs:[40], a store and a compare of a copy, a jne and a call of __stack_chk_fail. Explain them and how you would find the cause.

    What it tests
    Knowing what -fcf-protection and -fstack-protector-strong emit, why endbr64 is safe on older processors, where the canary and its copy live, and how to find a compiler's default options instead of guessing.
    First step
    Name the two features from their instructions, then ask each compiler which of them it turns on by default with gcc -Q --help=common, and compare that with the flags each build passes.
    Follow-ups

    From the lesson Reading compiler output: -O0 versus -O2

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  3. Fresher to senior

    Stepping through an -O2 -g build of a summing loop, the debugger's current line goes 3, 4, 3, 4, 3, some steps land on no line at all, and printing the loop counter shows "optimized out". A teammate says the debug information is broken. What is happening, and how do you show it?

    What it tests
    Reading the line table of optimised code: rows interleaved by scheduling, line 0 rows, is_stmt flags, and variables that live in a register for part of a function or not at all; choosing a build for stepping.
    First step
    Dump the line table with llvm-dwarfdump --debug-line and read it in address order beside llvm-objdump -S: if the rows alternate between lines 3 and 4 exactly where the loop's instructions alternate, the table is right and the debugger is reporting optimised code faithfully.
    Follow-ups

    From the lesson Debug info: symbols and source mapping

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  4. Mid to senior

    A crash in a service gives a backtrace that is correct up to a hand-written assembly routine and then stops or shows garbage frames above it, even though the binary was built with -g. What is the likely cause and how do you confirm and fix it?

    What it tests
    Knowing that unwinding needs call-frame information per function, that -g on C does not supply it for hand-written assembly, how to read an FDE, and how to add correct .cfi directives.
    First step
    Check whether the assembly routine on the boundary has CFI: dump the binary's unwind section and look for an FDE covering its address range; a routine with none is where an unwinder loses the stack.
    Follow-ups

    From the lesson Debug info: symbols and source mapping

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  5. Mid to staff

    You own a native binary shipped to many machines and must be able to turn a crash report into a function and line. How do you build, ship and symbolise it so the shipped file stays small and crashes are still readable?

    What it tests
    Splitting debug info, archiving it by build id, stripping the shipped binary, accounting for the load slide of a PIE, and matching the right debug file to a build.
    First step
    Build with -O2 -g, split the debug info into a dSYM or .debug file, ship the stripped binary, and archive the debug file under the build's UUID so a later crash can be symbolised against it.
    Follow-ups

    From the lesson Debug info: symbols and source mapping

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  6. Mid to senior

    A stripped x86-64 Linux build of a C++ service crashes in a function whose listing is mov rax, qword ptr [rdi] then call qword ptr [rax + 0x28]. There are no symbol names. How do you work out which virtual method that call reaches, and for which class?

    What it tests
    Reading a virtual call's slot offset back to a method, knowing that the object's vtable pointer decides the class at run time, and recovering a class name from a binary without symbols.
    First step
    Separate the two questions. The slot offset 0x28 is fixed at compile time and names one method of the pointer's static type; the class comes from the object at run time. So read the offset against the hierarchy's declarations, then take the object's vtable pointer from the core dump.
    Follow-ups

    From the lesson C++ and Rust in assembly: names, vtables, checks

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 4 minutes
  7. Mid to senior

    A profile of a Rust image filter puts most of its time in a loop that computes out[i] = a[i] + b[i] for i in 0..width over three slices. A teammate proposes get_unchecked everywhere to remove the bounds checks. What do you check first, and what would you try before unsafe code?

    What it tests
    Reading where a release build keeps bounds checks in a loop and whether the hot part is vectorised, and knowing the safe rewrites, reslicing to the bound or zipping iterators, that let the compiler drop the checks.
    First step
    Compile the function with the release profile's flags to assembly and find the loop by its backward branch. Note whether the body uses vector registers, and count the compares against slice lengths inside it. Only compares that run on every iteration of the hot path are worth removing.
    Follow-ups

    From the lesson C++ and Rust in assembly: names, vtables, checks

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  8. Senior to staff

    A C++ simulation calls body->step() through a base-class pointer for millions of objects per frame, and the profile shows the indirect call among the top costs. Which ways can make that call direct or cheaper, what does each need, and how do you confirm the result in the listing?

    What it tests
    Knowing which facts let a compiler prove the target of a virtual call, what each costs or requires, why an indirect call is expensive in a hot loop, and confirming from the listing that the lookup is gone.
    First step
    Read the call in the listing of the real build: a load from the object, a load from the vtable and an indirect call mean the compiler could not prove the target. Then list what it would need to know about the object's class, and which of those facts the code or the build can supply.
    Follow-ups

    From the lesson C++ and Rust in assembly: names, vtables, checks

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  9. Mid to senior

    A C function rejects bad input with if (a + b < a) return -1; for int a and a non-negative int b. Its unit test of an overflowing pair passes in the debug build and fails in the -O2 release build. Explain what happened, how you would show it, and how you would fix it for every build.

    What it tests
    Knowing that signed overflow is undefined, reading the deleted comparison in a listing, using a sanitizer to show the overflow, and replacing the test with one that cannot overflow or with checked arithmetic.
    First step
    Compile the function with the release flags to assembly and look for the comparison: if the listing tests only the sign of b, the compiler removed the overflow case because it may assume signed addition never overflows.
    Follow-ups

    From the lesson Optimisation and undefined behaviour in assembly

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  10. Mid to senior

    A library's README says its hot summing loop is vectorised at -O2. Your product builds it with Clang on one platform and GCC on another. How do you confirm the claim for both builds, and what do you check if one of them is not vectorised?

    What it tests
    Reading vector registers and leftover loops in a listing, asking each compiler for its vectoriser remarks, knowing that cost models and levels differ between compilers, and the usual blockers such as aliasing.
    First step
    Build each platform's object with its real flags and compiler, ask each compiler to report what its vectoriser did with the loop, and read the loop in each listing for vector registers.
    Follow-ups

    From the lesson Optimisation and undefined behaviour in assembly

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  11. Senior to staff

    A teammate rewrote a summing loop with NEON intrinsics and expects it to beat the compiler's code. How do you decide whether the rewrite helps, and why could it be slower than what the compiler produced from plain C?

    What it tests
    Comparing the listings of both versions, counting the work each loop iteration does, measuring the two fairly, and weighing what intrinsics cost to keep and to port.
    First step
    Before timing anything, compile the plain loop with the release flags and read its listing: if the compiler already vectorised it, compare the two loop bodies instruction by instruction, counting elements per iteration and accumulators.
    Follow-ups

    From the lesson Optimisation and undefined behaviour in assembly

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  12. Mid to senior

    A stripped x86-64 binary has a function of one unsigned int: mov ecx, edi; mov eax, 2863311531; imul rax, rcx; shr rax, 33; ret. It contains no divide. What does it compute, how would you show that your reading holds for every input, and what would change for a signed int?

    What it tests
    Recognising reciprocal multiplication, recovering the divisor from the constant and the shift, proving the result exact for all inputs rather than sampling, and the correction signed division needs for negative numbers.
    First step
    Treat it as a reciprocal multiplication: the shift gives the power of two, and dividing that power by the constant gives the divisor. Then prove that the rounding of the constant is too small to change any 32-bit result, rather than testing a handful of values.
    Follow-ups

    From the lesson Compiler idioms you will meet

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  13. Mid to senior

    A teammate wants every int index in a hot C loop changed to long, because int indexes cost a sign extension on 64-bit machines. Another says the compiler removes that cost anyway. Using the code compilers emit for x86-64, AArch64 and RISC-V, when is each of them right, and how would you settle it for one loop?

    What it tests
    Knowing why a 32-bit signed index needs widening before it addresses memory, which ISAs fold or avoid the extension, when a compiler can remove it from a loop, and checking the listing rather than guessing.
    First step
    Compile the loop with the real flags for each target and look for the extension: movsxd on x86-64, sxtw on AArch64, and on RISC-V the W forms such as addw that extend as they compute. Then check whether each one sits inside the loop body or runs once before it.
    Follow-ups

    From the lesson Compiler idioms you will meet

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes
  14. Senior to staff

    The boot firmware of a 64-bit RISC-V board must fit in 128 KB of on-chip memory and has grown to 150 KB. It is built with -O2 -march=rv64g. What would you try, in what order, and how would you measure what each step saves?

    What it tests
    Knowing what the compressed extension saves and that the core must implement it, the size levels -Os and -Oz and what they cost in speed, removing unused code at link time, and measuring sizes per section and per function.
    First step
    Measure before changing anything: the size of every section with llvm-size, and the largest functions with llvm-nm --size-sort. Then confirm that the board's core implements the C extension, because rebuilding for rv64gc changes no source and the RISC-V manual puts its typical saving at 25 to 30 %.
    Follow-ups

    From the lesson Compiler idioms you will meet

    Model answer, Pro.

    In the answer:

    • Model answer
    • Common mistakes
    • Rubric
    • Variants
    • about 3 minutes

All Assembly (x86-64, AArch64, RISC-V) interview questions · The Assembly (x86-64, AArch64, RISC-V) track

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.