Assembly Explorer
Compiles the C or C++ file you are editing with
Compiler Explorer and shows the assembly next to it.
There is no local toolchain to install. The source is sent to the public Compiler
Explorer API and the assembly comes back over the network.
What it does
- Compile. Run Assembly Explorer: Compile to Assembly on a
.c or .cpp file
(Ctrl+Alt+A, or right-click and pick Compile to Assembly). The assembly
opens in a read-only panel beside your code.
- Compile locally. Assembly Explorer: Compile to Assembly can also run a compiler from
your own
PATH instead of Compiler Explorer, which needs no network.
- Hover an instruction. The tooltip shows the source line the compiler
attributed it to, its
file:line:column, and a link to jump there.
- Hover your own code. The tooltip shows the instructions that line produced.
If it produced none, it says so.
- Select lines. The matching lines on the other side light up, and the panel
scrolls them into view.
- Click a line. The other pane's cursor moves to the linked line, so both
panes show one highlight in the same place.
- Jump both ways. Assembly Explorer: Go to Source from the panel, Assembly Explorer: Go to
Assembly from the source.
- Follow a label. Ctrl+click or
F12 on a label jumps to its definition,
and back again. The cursor on a label highlights every place it is used.
- Stays current. Once a file has been compiled, the panel follows your edits
on save.
- Warnings are shown. A build that succeeds with warnings puts a warning icon
in the status bar and lists them in its tooltip.
- Remembers what it built. Closing a file keeps its assembly, so reopening it
is instant. Saving a new file keeps the build it had as an unsaved buffer.
- Skips work it does not need. Asking for the assembly again when the source
and the flags have not changed shows the panel from memory.
Usage
Open a C or C++ file and press Ctrl+Alt+A (macOS: Cmd+Alt+A).
An Assembly Explorer output channel records each compilation and any compiler diagnostics.
Commands
| Command |
Description |
Assembly Explorer: Compile to Assembly |
Compile the active file and open the assembly panel |
Assembly Explorer: Optimization Level |
Pick -O0…-O3, -Os, -Og, or type your own flags |
Assembly Explorer: Edit Compiler Flags |
Type the flags directly, like the flags box on the web |
Assembly Explorer: Toggle Optimization (-O0 / -O2) |
Flip between the two, keeping your other flags |
Assembly Explorer: Auto Compile Mode |
Choose on save or off |
Assembly Explorer: Select Compiler |
Choose the compiler to build with |
Assembly Explorer: Toggle Intel / AT&T Syntax |
Switch assembly syntax and recompile |
Assembly Explorer: Go to Source |
From the assembly panel, jump to the producing source line |
Assembly Explorer: Go to Assembly |
From source, jump to the generated instructions |
The everyday commands are also in an Assembly Explorer submenu in the editor context
menu.
Where things open
Your code and its assembly each keep their own column.
- Compiling opens the panel beside your code and does not steal focus. The cursor
stays where you were typing.
- A hover link or Assembly Explorer: Go to Source returns to the column your code is in,
never into the panel.
- The panel is placed once and then left alone. Switching files does not swap it,
and opening a file over it does not move that file, so your layout stays put.
To see another file's assembly, compile it or click its panel tab. To stop
files opening in the panel's column at all, use VS Code's own View: Lock
Editor Group on that column.
This matters because VS Code resolves both ViewColumn.Beside and an unset
column against whichever group has focus. Leaving the column out gives a
different answer depending on where you last clicked, so the extension always
names the column.
What happens as files come and go
- Closing a file closes its panel too. The build itself is kept, in memory and in
the saved workspace state, so reopening the file and pressing
Ctrl+Alt+A
shows the assembly again without a round trip. Closing the panel never closes
your code. Results are dropped only once the store passes its cap, oldest
first, and never for a file whose code or panel is on screen.
- Asking again does not necessarily compile.
Ctrl+Alt+A reuses the build in
memory when the source text and the flags both match it, and compiles only when
the answer would differ.
- Saving a new file carries its build onto the file. Compile an unsaved buffer
and it gets a panel named after it, such as
Untitled-1.asm. Save it as
main.c and the result and the auto-compile tracking move to the real file,
the panel becomes main.asm and its old tab is closed. Renaming a file on
disk works the same way.
Optimization level
The level is a dial, not a decision. The status bar shows the flags in use next
to the compiler. Click it to open a picker with -O0, -O1, -O2, -O3,
-Os, -Og, or Other flags… to type your own. Ctrl+Alt+O (macOS:
Cmd+Alt+O) flips between -O0 and -O2. Assembly Explorer: Edit Compiler Flags skips
the picker and opens the flags box straight away, the way you would type them on
the website.
Changing the level rebuilds immediately, with no edit needed. Your other flags
are kept: switching with -Wall -O2 gives -Wall -O0.
The default is -O2. That is where the assembly stops being a direct translation
of your source: calls get folded away, variables stop existing, and some lines
produce nothing at all. Use -O0 when you want the opposite, a listing where
every statement maps to real instructions.
Auto compile
Once a file has been compiled, the extension keeps its panel up to date on its
own, the way godbolt.org does. Choose a mode with Assembly Explorer: Auto Compile Mode
or the godbolt.autoCompile setting.
| Mode |
Behaviour |
onSave |
Rebuild when you save the file (default) |
off |
Only rebuild when you ask |
Only files that have already been compiled once are watched, so saving an
unrelated C file never contacts the API. Background rebuilds never open a panel
or steal focus; the assembly is simply up to date the next time you look at it. A
background build that fails stays quiet: the reason goes to the assembly panel
and the output channel instead of a popup, and the status bar shows an error
icon.
Compiling locally
By default the source goes to Compiler Explorer. A compiler on your machine can
do it instead, which needs no network: run Assembly Explorer: Select Compiler and pick
Compile locally at the top of the list. The choice is remembered, and the
godbolt.compilerMode setting decides the mode until you first switch.
In local mode the compiler setting names the binary, for example gcc or
/usr/bin/clang++, and the compiler picker lists the ones found on your PATH,
family-appropriately: gcc, clang or cc for C, g++, clang++ or c++ for
C++.
Everything else behaves the same, including the optimization dial, the flags and
the filters. The source mapping works the same way too: the listing is asked for
with -g -S, and each instruction's line and column come from the .loc
directives the compiler emits, so the panel, hover, linked highlighting and
labels all apply as they do in remote mode.
Labels
The listing is full of names: function labels like main: and square(int):,
string and constant labels like .LC0:, and the instructions that refer to them.
Ctrl+click a label, or press F12, to jump to its definition; on the definition
itself, F12 jumps back to the places that use it. Put the cursor on a label and
every occurrence is highlighted, with Shift+F12 listing them all in the usual
way. Hovering a label says whether it is a definition or a use, and shows the
other side.
A plain click is left alone: it moves the cursor, which is what keeps the two
panes linked.
Hovering your own code
Hover a line of C or C++ and the tooltip shows the instructions that line
produced, with a link that jumps to them in the panel. This is the same
relationship the assembly hover describes, read from the other end.
The case worth knowing about is the empty one. When a line produces no
instructions, the tooltip says the compiler removed it instead of staying silent.
Silence would be ambiguous, since it looks the same as a file that has not been
compiled. Optimization is the usual reason, so the tooltip also offers a
comparison against -O0.
Hovering is turned off once you have edited the file without rebuilding it. The
mapping describes the text that was compiled, so after an edit the line numbers
mean something different. Save the file, or press Ctrl+Alt+A, and the hovers
come back. A hover never appears for a file with no build, or for a build that
failed.
Settings
All under godbolt.* in Settings.
| Setting |
Default |
Description |
compiler |
g162 |
Compiler Explorer compiler id (x86-64 gcc 16.2) |
compilerMode |
remote |
Starting mode; the picker switches and remembers it |
userArguments |
-O2 |
Flags passed to the compiler; the picker edits this |
intelSyntax |
true |
Intel syntax instead of AT&T |
filterDirectives |
true |
Hide directives and debug sections, keep only instns |
filterLabels |
true |
Hide compiler-generated labels such as .LFB0 |
filterComments |
true |
Hide the source lines the compiler echoes back |
trim |
false |
Trim unused code |
autoCompile |
onSave |
onSave or off; see above |
linkPanes |
true |
Highlight linked lines between the source and its assembly |
The three filter* settings mirror Compiler Explorer's own "Filter directives /
labels / comments" checkboxes. They remove the selected items, so turning one off
shows more of what the compiler emitted.
The godbolt.org website defaults to no optimization flags, while this extension
defaults to -O2. To match the website, set userArguments to empty. For
learning, -O0 is often more useful: the compiler stops rearranging your code and
the mapping becomes close to one line per statement.
Any compiler in the Compiler Explorer catalogue can be used. The Select Compiler
command searches the live list, fetched once per session, with a built-in fallback
list when offline. The picker is split by architecture, with x86-64 first, since
the catalogue also holds hundreds of cross-compilers (ARM, AVR, 6502, RISC-V and
others) that cannot produce x86 assembly at all.
Development
npm install
npm run compile # or: npm run watch
Press F5 to launch an Extension Development Host with the extension loaded,
then open examples/example.cpp or examples/hello.c. That window starts with no
folder open, so open the folder first. Development extensions do not appear in the
Extensions sidebar; the extension activates when you open a C or C++ file.
Tests
npm test # compile + unit tests + smoke test (all offline)
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run format # prettier --write
npm run unit # pure-logic tests
npm run smoke # activates the real extension against recorded responses
npm run smoke:live # same, but against the live Compiler Explorer API
npm run record # re-record test/fixtures/ce.json from the live API
npm run perf # micro-benchmarks for the store lookups
test/unit.js covers the pure helpers directly, notably the assembly-to-source
mapping.
test/smoke.js stubs the VS Code API, activates the real extension and asserts on
the mapping, hover contents, selection sync, auto-compile behaviour and state
restoration across a simulated window reload.
Compiler Explorer is replayed from test/fixtures/ce.json, so the suite needs no
network and cannot fail because someone else's server is down. The fixtures are
real recorded responses: the C and C++ compiler lists plus every compilation the
suite performs. Three modes, set by GODBOLT_CE or as the first argument to the
script:
| Mode |
What it does |
replay (default) |
Serves every request from the fixtures. A miss fails with the request that was not recorded. |
live |
Ignores the fixtures and calls the real API (npm run smoke:live). |
record |
Calls the real API and merges the responses back into the fixtures (npm run record), so a partial run only ever adds to them. |
Re-record when the suite grows a new request, or when you want to move to a newer
compiler. Expect a diff: Compiler Explorer's compiler lists change as it adds and
retires compilers, and the listing itself is part of what is recorded.
test/perf.js measures the two lookups that run the most, the per-file result
lookup and the assembly-document lookup, and also times the scan-and-sort they
replaced, so a regression back to scanning the whole store is obvious.
Building a VSIX
npm run package # writes assembly-explorer-<version>.vsix
npm run install:local # installs that file, and trashes older installed copies
Then reload the VS Code window.
Installing does not replace the previous version, it sits beside it, so
install:local clears the older copies out. A window that can see two of them may
read its settings from one and run the code from the other, which is how a setting
can be declared and still be rejected as unregistered.
How it works
src/core/: shared foundations, used by every other module.
services.ts: the process-wide singletons, populated once at activation.
config.ts: reads the godbolt.* settings and resolves the workspace/global
target for writing them.
constants.ts: shared constants: language ids, known x86 instruction sets,
the default compiler and flags.
types.ts: the shared types the modules pass around.
documents.ts: which documents can be compiled, their language, and names.
text.ts: text helpers: ANSI stripping, statement detection, snapshot checks.
warnings.ts: once-per-key user warnings, so background builds stay quiet.
src/compiler/: talking to Compiler Explorer and turning a document into a build.
client.ts: HTTP client for godbolt.org/api.
compilers.ts: the built-in fallback list used when the catalogue cannot be
fetched.
compilerRegistry.ts: picks the compiler id that handles the file's language.
compilerSelection.ts: compiler choice: language detection, resolving the
configured id, and the compiler picker.
compile.ts: the compile path: building a document and opening its panel.
compilerMode.ts: the one branch between Compiler Explorer and a local compiler.
localCompiler.ts: running a compiler from PATH and reading the .loc
directives it emits back into a source mapping.
optimization.ts: the optimization dial: rewriting flags and the level picker.
src/asm/: the assembly side: the store, the godbolt-asm: documents and the
asm↔source mapping.
resultStore.ts: holds compilation results and the asm↔source index.
serialize.ts: indexes, restores and serialises the lines and mapping a result
holds.
asmUri.ts: the godbolt-asm: URI scheme and the keys that identify a
document.
mapping.ts: resolves an assembly line (including labels) to its source.
documentProvider.ts: serves the read-only godbolt-asm: documents.
src/panel/: everything about the panel beside your code.
panelLayout.ts: which column holds the code and which holds the assembly.
panelFiles.ts: closing, renaming and adopting panels as files come and go.
syncHighlight.ts: keeps selection highlighting in step on both sides.
navigation.ts: jumping between source and assembly.
statusBar.ts: the one-line summary, its warnings and its failure state.
src/link/: the label index and everything you can hover or click in the listing.
hoverProvider.ts: both hover tooltips: the panel's, which names the producing
source line, and the source's, which names what the line produced.
labels.ts: the label index, and the definition and reference answers built on
it.
labelClicks.ts: the Ctrl+click and F12 jumps between a label and its uses.
src/state/: what runs in the background and what survives a window reload.
workspaceState.ts: what survives a window reload, and when it is written.
autoCompile.ts: rebuilds already-compiled files when they are saved.
src/extension.ts: activation and wiring only; the composition root.
Staying cheap
Two paths run the most: the per-file result lookup, since the auto-compiler asks
"has this file been compiled?" whenever a file is saved, and the selection
re-highlight. Both are cheap on purpose.
- The store indexes results per source file, so a lookup costs one map hit instead
of scanning and sorting every result it holds.
- Highlights are rebuilt only when something they depend on changed. VS Code fires
selection events even for a cursor that has not moved, and each rebuild costs a
round trip per decorated editor.
- Workspace state is written at most once per change, as a single already-encoded
string, so saving does not re-encode every result it holds.
npm run perf prints the numbers. getBySource is more than an order of
magnitude faster than the scan it replaced.
Compiler Explorer returns the assembly as a list of lines, each optionally tagged
with the source line and column it came from. That mapping is what makes all of the
above possible. Without it the assembly is just text.
Compilation results are kept in the workspace state, so assembly panels that VS
Code restores after a reload come back with their contents instead of an empty
placeholder. Files that changed while the window was closed are rebuilt quietly;
the rest are not touched.
Two grammar files are contributed: syntaxes/x86asm.tmLanguage.json for x86-64,
and a deliberately architecture-neutral syntaxes/asm-generic.tmLanguage.json used
when the compiler targets anything else, since applying x86 rules to ARM or 6502
output colours the wrong things.
Compiler ids and languages
Compiler Explorer keeps a separate compiler id per language: g162 is the C++
build of gcc 16.2 and cg162 is the C build (vcpp_v19_latest_x64 pairs with
vc_v19_latest_x64). The compile endpoint's lang field does not switch
language. Handing a C++-only id a C file makes it compile the file as C++, which
mangles names and rejects C that is not valid C++.
The extension therefore stores one compiler id and translates it to whichever
variant matches the file you are compiling, matching the two on their display
name. The status bar shows the id actually used, and the picker is filtered to the
language of the current file.
Notes and limitations
- Only C and C++ are wired up so far, although the API supports many more.
- Compilation needs network access, or a compiler of your own. By default the work
is done by Compiler Explorer; set
godbolt.compilerMode to local to run a
compiler from your PATH instead, which needs no network at all.
- The source sent is the current editor buffer, including unsaved edits. That is
what lets the panel keep up as you type.
- Assembly is attributed by the compiler's own debug information, so under
aggressive optimization a line may map to a surprising place, or nowhere.
- Labels,
.string data and directives carry no source information of their own.
Selecting or hovering one resolves to the nearest instruction's source line,
which is what makes clicking a function label in the assembly useful. The hover
says when it is showing an approximation.
- A compiler built for a different CPU (ARM, 6502, AVR and so on) returns assembly
that is not x86. The picker separates those out, the extension warns once if you
compile with one, and the listing gets the architecture-neutral grammar instead
of the x86 one.
- A build that a newer one replaces is cancelled, and the newer result wins. A very
large file on a slow connection still holds a request open until it is replaced
or times out after 30 seconds.
- Panel names drop the source extension, so
tes.c and tes.cpp in the same
folder both want a tab called tes.asm. They stay separate documents and nothing
is lost, but the tabs are hard to tell apart by name.