0% found this document useful (0 votes)
7 views

4-Tutorial Part 2 - Creating A Trivial Machine Code Function - Libgccjit 13.2.0 Documentation

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

4-Tutorial Part 2 - Creating A Trivial Machine Code Function - Libgccjit 13.2.0 Documentation

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1

libgccjit 13.2.

0 ( ) documentation » Tutorial » Tutorial part 2: Creating a trivial machine code function previous | next | index

Tutorial part 2: Creating a trivial machine code function Table of Contents

Consider this C function: Tutorial part 2: Creating a


trivial machine code function
Error-handling
int square (int i)
Options
{
Full example
return i * i;
}
Previous topic
How can we construct this at run-time using libgccjit?
Tutorial part 1: “Hello world”
First we need to include the relevant header:
Next topic
#include <libgccjit.h>
Tutorial part 3: Loops and
variables
All state associated with compilation is associated with a gcc_jit_context*.

Create one using gcc_jit_context_acquire(): This Page

Show Source
gcc_jit_context *ctxt;
ctxt = gcc_jit_context_acquire (); Quick search

The JIT library has a system of types. It is statically-typed: every expression is of a specific type, Go
fixed at compile-time. In our example, all of the expressions are of the C int type, so let’s
obtain this from the context, as a gcc_jit_type*, using gcc_jit_context_get_type():

gcc_jit_type *int_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);

gcc_jit_type* is an example of a “contextual” object: every entity in the API is associated with a
gcc_jit_context*.

Memory management is easy: all such “contextual” objects are automatically cleaned up for you
when the context is released, using gcc_jit_context_release():

gcc_jit_context_release (ctxt);

so you don’t need to manually track and cleanup all objects, just the contexts.

Although the API is C-based, there is a form of class hierarchy, which looks like this:

+- gcc_jit_object
+- gcc_jit_location
+- gcc_jit_type
+- gcc_jit_struct
+- gcc_jit_field
+- gcc_jit_function
+- gcc_jit_block
+- gcc_jit_rvalue
+- gcc_jit_lvalue
+- gcc_jit_param

There are casting methods for upcasting from subclasses to parent classes. For example,
gcc_jit_type_as_object():

gcc_jit_object *obj = gcc_jit_type_as_object (int_type);

One thing you can do with a gcc_jit_object* is to ask it for a human-readable description,
using gcc_jit_object_get_debug_string():

printf ("obj: %s\n", gcc_jit_object_get_debug_string (obj));

giving this text on stdout:

obj: int

This is invaluable when debugging.

Let’s create the function. To do so, we first need to construct its single parameter, specifying its
type and giving it a name, using gcc_jit_context_new_param():

gcc_jit_param *param_i =
gcc_jit_context_new_param (ctxt, NULL, int_type, "i");

Now we can create the function, using gcc_jit_context_new_function():

gcc_jit_function *func =
gcc_jit_context_new_function (ctxt, NULL,
GCC_JIT_FUNCTION_EXPORTED,
int_type,
"square",
1, &param_i,
0);

To define the code within the function, we must create basic blocks containing statements.

Every basic block contains a list of statements, eventually terminated by a statement that either
returns, or jumps to another basic block.

Our function has no control-flow, so we just need one basic block:

gcc_jit_block *block = gcc_jit_function_new_block (func, NULL);

Our basic block is relatively simple: it immediately terminates by returning the value of an
expression.

We can build the expression using gcc_jit_context_new_binary_op():

gcc_jit_rvalue *expr =
gcc_jit_context_new_binary_op (
ctxt, NULL,
GCC_JIT_BINARY_OP_MULT, int_type,
gcc_jit_param_as_rvalue (param_i),
gcc_jit_param_as_rvalue (param_i));

A gcc_jit_rvalue* is another example of a gcc_jit_object* subclass. We can upcast it using


gcc_jit_rvalue_as_object() and as before print it with gcc_jit_object_get_debug_string().

printf ("expr: %s\n",


gcc_jit_object_get_debug_string (
gcc_jit_rvalue_as_object (expr)));

giving this output:

expr: i * i

Creating the expression in itself doesn’t do anything; we have to add this expression to a
statement within the block. In this case, we use it to build a return statement, which terminates
the basic block:

gcc_jit_block_end_with_return (block, NULL, expr);

OK, we’ve populated the context. We can now compile it using gcc_jit_context_compile():

gcc_jit_result *result;
result = gcc_jit_context_compile (ctxt);

and get a gcc_jit_result*.

At this point we’re done with the context; we can release it:

gcc_jit_context_release (ctxt);

We can now use gcc_jit_result_get_code() to look up a specific machine code routine within
the result, in this case, the function we created above.

void *fn_ptr = gcc_jit_result_get_code (result, "square");


if (!fn_ptr)
{
fprintf (stderr, "NULL fn_ptr");
goto error;
}

We can now cast the pointer to an appropriate function pointer type, and then call it:

typedef int (*fn_type) (int);


fn_type square = (fn_type)fn_ptr;
printf ("result: %d", square (5));

result: 25

Once we’re done with the code, we can release the result:

gcc_jit_result_release (result);

We can’t call square anymore once we’ve released result.

Error-handling
Various kinds of errors are possible when using the API, such as mismatched types in an
assignment. You can only compile and get code from a context if no errors occur.

Errors are printed on stderr; they typically contain the name of the API entrypoint where the error
occurred, and pertinent information on the problem:

./buggy-program: error: gcc_jit_block_add_assignment: mismatching types: assignment to

The API is designed to cope with errors without crashing, so you can get away with having a
single error-handling check in your code:

void *fn_ptr = gcc_jit_result_get_code (result, "square");


if (!fn_ptr)
{
fprintf (stderr, "NULL fn_ptr");
goto error;
}

For more information, see the error-handling guide within the Topic eference.

Options
To get more information on what’s going on, you can set debugging flags on the context using
gcc_jit_context_set_bool_option().

Setting GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE will dump a C-like representation to stderr


when you compile (GCC’s “GIMPLE” representation):

gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
1);
result = gcc_jit_context_compile (ctxt);

square (signed int i)


{
signed int D.260;

entry:
D.260 = i * i;
return D.260;
}

We can see the generated machine code in assembler form (on stderr) by setting
GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE on the context before compiling:

gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
1);
result = gcc_jit_context_compile (ctxt);

.file "fake.c"
.text
.globl square
.type square, @function
square:
.LFB6:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
.L14:
movl -4(%rbp), %eax
imull -4(%rbp), %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size square, .-square
.ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2)"
.section .note.GNU-stack,"",@progbits

By default, no optimizations are performed, the equivalent of GCC’s -O0 option. We can turn
things up to e.g. -O3 by calling gcc_jit_context_set_int_option() with
GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL:

gcc_jit_context_set_int_option (
ctxt,
GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
3);

.file "fake.c"
.text
.p2align 4,,15
.globl square
.type square, @function
square:
.LFB7:
.cfi_startproc
.L16:
movl %edi, %eax
imull %edi, %eax
ret
.cfi_endproc
.LFE7:
.size square, .-square
.ident "GCC: (GNU) 4.9.0 20131023 (Red Hat 0.2)"
.section .note.GNU-stack,"",@progbits

Naturally this has only a small effect on such a trivial function.

Full example
Here’s what the above looks like as a complete program:

/* Usage example for libgccjit.so


Copyright (C) 2014-2023 Free Software Foundation, Inc.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it


under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GCC is distributed in the hope that it will be useful, but


WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */

#include <libgccjit.h>

#include <stdlib.h>
#include <stdio.h>

void
create_code (gcc_jit_context *ctxt)
{
/* Let's try to inject the equivalent of:

int square (int i)


{
return i * i;
}
*/
gcc_jit_type *int_type =
gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
gcc_jit_param *param_i =
gcc_jit_context_new_param (ctxt, NULL, int_type, "i");
gcc_jit_function *func =
gcc_jit_context_new_function (ctxt, NULL,
GCC_JIT_FUNCTION_EXPORTED,
int_type,
"square",
1, &param_i,
0);

gcc_jit_block *block = gcc_jit_function_new_block (func, NULL);

gcc_jit_rvalue *expr =
gcc_jit_context_new_binary_op (
ctxt, NULL,
GCC_JIT_BINARY_OP_MULT, int_type,
gcc_jit_param_as_rvalue (param_i),
gcc_jit_param_as_rvalue (param_i));

gcc_jit_block_end_with_return (block, NULL, expr);


}

int
main (int argc, char **argv)
{
gcc_jit_context *ctxt = NULL;
gcc_jit_result *result = NULL;

/* Get a "context" object for working with the library. */


ctxt = gcc_jit_context_acquire ();
if (!ctxt)
{
fprintf (stderr, "NULL ctxt");
goto error;
}

/* Set some options on the context.


Let's see the code being generated, in assembler form. */
gcc_jit_context_set_bool_option (
ctxt,
GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
0);

/* Populate the context. */


create_code (ctxt);

/* Compile the code. */


result = gcc_jit_context_compile (ctxt);
if (!result)
{
fprintf (stderr, "NULL result");
goto error;
}

/* We're done with the context; we can release it: */


gcc_jit_context_release (ctxt);
ctxt = NULL;

/* Extract the generated code from "result". */


void *fn_ptr = gcc_jit_result_get_code (result, "square");
if (!fn_ptr)
{
fprintf (stderr, "NULL fn_ptr");
goto error;
}

typedef int (*fn_type) (int);


fn_type square = (fn_type)fn_ptr;
printf ("result: %d\n", square (5));

error:
if (ctxt)
gcc_jit_context_release (ctxt);
if (result)
gcc_jit_result_release (result);
return 0;
}

Building and running it:

$ gcc \
tut02-square.c \
-o tut02-square \
-lgccjit

# Run the built program:


$ ./tut02-square
result: 25

libgccjit 13.2.0 ( ) documentation » Tutorial » Tutorial part 2: Creating a trivial machine code function previous | next | index

© Copyright 2014-2023 Free Software Foundation, Inc.. Created using Sphinx 5.3.0.

You might also like