AsmJit
Low-Latency Machine Code Generation
X86/X64 builder implementation.
The code representation used by BaseBuilder is compatible with everything AsmJit provides. Each instruction is stored as InstNode, which contains instruction id, options, and operands. Each instruction emitted will create a new InstNode instance and add it to the current cursor in the double-linked list of nodes. Since the instruction stream used by BaseBuilder can be manipulated, we can rewrite the SumInts example from Assembler into the following:
When the example is executed it should output the following (this one using AMD64-SystemV ABI):
The number of use-cases of BaseBuilder is not limited and highly depends on your creativity and experience. The previous example can be easily improved to collect all dirty registers inside the function programmatically and to pass them to FuncFrame::setDirtyRegs().
Even when BaseAssembler and BaseBuilder provide the same interface as defined by BaseEmitter their platform dependent variants like x86::Assembler and x86::Builder cannot be interchanged or casted to each other by using a C++ static_cast<>
. The main reason is the inheritance graph of these classes is different and cast-incompatible, as illustrated below:
The graph basically shows that it's not possible to cast between x86::Assembler and x86::Builder. However, since both share the base interface (BaseEmitter) it's possible to cast them to a class that cannot be instantiated, but defines the same interface - the class is called x86::Emitter and was introduced to make it possible to write a function that can emit to both x86::Assembler and x86::Builder. Note that x86::Emitter cannot be created, it's abstract and has private constructors and destructors; it was only designed to be casted to and used as an interface.
Each architecture-specific emitter implements a member function called as<arch::Emitter>()
, which casts the instance to the architecture specific emitter as illustrated below:
The example above shows how to create a function that can emit code to either x86::Assembler or x86::Builder through x86::Emitter, which provides emitter-neutral functionality. x86::Emitter, however, doesn't provide any emitter-specific functionality like setCursor()
.
BaseBuilder emitter stores its nodes in a double-linked list, which makes it easy to manipulate that list during the code generation or afterwards. Each node is always emitted next to the current cursor and the cursor is advanced to that newly emitted node. The cursor can be retrieved and changed by BaseBuilder::cursor() and BaseBuilder::setCursor(), respectively.
The example below demonstrates how to remember a node and inject something next to it.
The function above would actually emit the following:
Called after the emitter was attached to CodeHolder
.
Reimplemented from asmjit::BaseBuilder.
Called after the emitter was detached from CodeHolder
.
Reimplemented from asmjit::BaseBuilder.
Finalizes this emitter.
Materializes the content of the emitter by serializing it to the attached CodeHolder through an architecture specific BaseAssembler. This function won't do anything if the emitter inherits from BaseAssembler as assemblers emit directly to a CodeBuffer held by CodeHolder. However, if this is an emitter that inherits from BaseBuilder or BaseCompiler then these emitters need the materialization phase as they store their content in a representation not visible to CodeHolder.
Reimplemented from asmjit::BaseEmitter.
© 2018-2024 AsmJit Authors | GitHub | Chat | Blog
AsmJit and AsmTK libraries are open source software released under the Zlib license and can be used safely in any open-source or commercial product, statically or dynamically linked, and without having to advertise the use of the libraries. Code snippets and examples are released into public domain, see Unlicense for more details.