Using Assembly Language with libgccjit++

libgccjit has some support for directly embedding assembler instructions. This is based on GCC’s support for inline asm in C code, and the following assumes a familiarity with that functionality. See How to Use Inline Assembly Language in C Code in GCC’s documentation, the “Extended Asm” section in particular.

Adding assembler instructions within a function

class gccjit::extended_asm

A gccjit::extended_asm represents an extended asm statement: a series of low-level instructions inside a function that convert inputs to outputs.

gccjit::extended_asm is a subclass of gccjit::object. It is a thin wrapper around the C API’s gcc_jit_extended_asm *.

To avoid having an API entrypoint with a very large number of parameters, an extended asm statement is made in stages: an initial call to create the gccjit::extended_asm, followed by calls to add operands and set other properties of the statement.

There are two API entrypoints for creating a gccjit::extended_asm:

For example, to create the equivalent of:

       asm ("mov %1, %0\n\t"
            "add $1, %0"
            : "=r" (dst)
            : "r" (src));

the following API calls could be used:

    block.add_extended_asm ("mov %1, %0\n\t"
                            "add $1, %0")
    .add_output_operand ("=r", dst)
    .add_input_operand ("r", src);

Warning

When considering the numbering of operands within an extended asm statement (e.g. the %0 and %1 above), the equivalent to the C syntax is followed i.e. all output operands, then all input operands, regardless of what order the calls to gccjit::extended_asm::add_output_operand() and gccjit::extended_asm::add_input_operand() were made in.

As in the C syntax, operands can be given symbolic names to avoid having to number them. For example, to create the equivalent of:

       asm ("bsfl %[aMask], %[aIndex]"
            : [aIndex] "=r" (Index)
            : [aMask] "r" (Mask)
            : "cc");

the following API calls could be used:

    block.add_extended_asm ("bsfl %[aMask], %[aIndex]")
    .add_output_operand ("aIndex", "=r", index)
    .add_input_operand ("aMask", "r", mask)
    .add_clobber ("cc");
extended_asm gccjit::block::add_extended_asm(const std::string &asm_template, gccjit::location loc = location())

Create a gccjit::extended_asm for an extended asm statement with no control flow (i.e. without the goto qualifier).

The parameter asm_template corresponds to the AssemblerTemplate within C’s extended asm syntax. It must be non-NULL. The call takes a copy of the underlying string, so it is valid to pass in a pointer to an on-stack buffer.

extended_asm gccjit::block::end_with_extended_asm_goto(const std::string &asm_template, std::vector<block> goto_blocks, block *fallthrough_block, location loc = location())

Create a gccjit::extended_asm for an extended asm statement that may perform jumps, and use it to terminate the given block. This is equivalent to the goto qualifier in C’s extended asm syntax.

For example, to create the equivalent of:

       asm goto ("btl %1, %0\n\t"
                 "jc %l[carry]"
                 : // No outputs
                 : "r" (p1), "r" (p2)
                 : "cc"
                 : carry);

the following API calls could be used:

  const char *asm_template =
    (use_name
     ? /* Label referred to by name: "%l[carry]".  */
       ("btl %1, %0\n\t"
        "jc %l[carry]")
     : /* Label referred to numerically: "%l2".  */
       ("btl %1, %0\n\t"
        "jc %l2"));

  std::vector<gccjit::block> goto_blocks ({b_carry});
  gccjit::extended_asm ext_asm
    = (b_start.end_with_extended_asm_goto (asm_template,
					  goto_blocks,
					  &b_fallthru)
       .add_input_operand ("r", p1)
       .add_input_operand ("r", p2)
       .add_clobber ("cc"));

here referencing a gcc_jit_block named “carry”.

num_goto_blocks corresponds to the GotoLabels parameter within C’s extended asm syntax. The block names can be referenced within the assembler template.

fallthrough_block can be NULL. If non-NULL, it specifies the block to fall through to after the statement.

Note

This is needed since each gccjit::block must have a single exit point, as a basic block: you can’t jump from the middle of a block. A “goto” is implicitly added after the asm to handle the fallthrough case, which is equivalent to what would have happened in the C case.

gccjit::extended_asm &gccjit::extended_asm::set_volatile_flag(bool flag)

Set whether the gccjit::extended_asm has side-effects, equivalent to the volatile qualifier in C’s extended asm syntax.

For example, to create the equivalent of:

asm volatile ("rdtsc\n\t"    // Returns the time in EDX:EAX.
               "shl $32, %%rdx\n\t"  // Shift the upper bits left.
               "or %%rdx, %0"        // 'Or' in the lower bits.
               : "=a" (msr)
               :
               : "rdx");

the following API calls could be used:

  gccjit::extended_asm ext_asm
    = block.add_extended_asm
	("rdtsc\n\t"  /* Returns the time in EDX:EAX.  */
	 "shl $32, %%rdx\n\t"  /* Shift the upper bits left.  */
	 "or %%rdx, %0")  /* 'Or' in the lower bits.  */
    .set_volatile_flag (true)
    .add_output_operand ("=a", msr)
    .add_clobber ("rdx");

where the gccjit::extended_asm is flagged as volatile.

gccjit::extended_asm &gccjit::extended_asm::set_inline_flag(bool flag)

Set the equivalent of the inline qualifier in C’s extended asm syntax.

gccjit::extended_asm &gccjit::extended_asm::add_output_operand(const std::string &asm_symbolic_name, const std::string &constraint, gccjit::lvalue dest)

Add an output operand to the extended asm statement. See the Output Operands section of the documentation of the C syntax.

asm_symbolic_name corresponds to the asmSymbolicName component of C’s extended asm syntax, and specifies the symbolic name for the operand. See the overload below for an alternative that does not supply a symbolic name.

constraint corresponds to the constraint component of C’s extended asm syntax.

dest corresponds to the cvariablename component of C’s extended asm syntax.

// Example with a symbolic name ("aIndex"), the equivalent of:
//   : [aIndex] "=r" (index)
ext_asm.add_output_operand ("aIndex", "=r", index);

This function can’t be called on an asm goto as such instructions can’t have outputs; see the Goto Labels section of GCC’s “Extended Asm” documentation.

gccjit::extended_asm &gccjit::extended_asm::add_output_operand(const std::string &constraint, gccjit::lvalue dest)

As above, but don’t supply a symbolic name for the operand.

// Example without a symbolic name, the equivalent of:
//   : "=r" (dst)
ext_asm.add_output_operand ("=r", dst);
gccjit::extended_asm &gccjit::extended_asm::add_input_operand(const std::string &asm_symbolic_name, const std::string &constraint, gccjit::rvalue src)

Add an input operand to the extended asm statement. See the Input Operands section of the documentation of the C syntax.

asm_symbolic_name corresponds to the asmSymbolicName component of C’s extended asm syntax. See the overload below for an alternative that does not supply a symbolic name.

constraint corresponds to the constraint component of C’s extended asm syntax.

src corresponds to the cexpression component of C’s extended asm syntax.

// Example with a symbolic name ("aMask"), the equivalent of:
//   : [aMask] "r" (Mask)
ext_asm.add_input_operand ("aMask", "r", mask);
gccjit::extended_asm &gccjit::extended_asm::add_input_operand(const std::string &constraint, gccjit::rvalue src)

As above, but don’t supply a symbolic name for the operand.

// Example without a symbolic name, the equivalent of:
//   : "r" (src)
ext_asm.add_input_operand ("r", src);
gccjit::extended_asm &gccjit::extended_asm::add_clobber(const std::string &victim)

Add victim to the list of registers clobbered by the extended asm statement. See the Clobbers and Scratch Registers section of the documentation of the C syntax.

Statements with multiple clobbers will require multiple calls, one per clobber.

For example:

ext_asm.add_clobber ("r0").add_clobber ("cc").add_clobber ("memory");

Adding top-level assembler statements

In addition to creating extended asm instructions within a function, there is support for creating “top-level” assembler statements, outside of any function.

void gccjit::context::add_top_level_asm(const char *asm_stmts)

Create a set of top-level asm statements, analogous to those created by GCC’s “basic” asm syntax in C at file scope.

For example, to create the equivalent of:

     asm ("\t.pushsection .text\n"
          "\t.globl add_asm\n"
          "\t.type add_asm, @function\n"
          "add_asm:\n"
          "\tmovq %rdi, %rax\n"
          "\tadd %rsi, %rax\n"
          "\tret\n"
          "\t.popsection\n");

the following API calls could be used:

  ctxt.add_top_level_asm ("\t.pushsection .text\n"
                          "\t.globl add_asm\n"
                          "\t.type add_asm, @function\n"
                          "add_asm:\n"
                          "\tmovq %rdi, %rax\n"
                          "\tadd %rsi, %rax\n"
                          "\tret\n"
                          "\t# some asm here\n"
                          "\t.popsection\n");