If you have entered the world of programming, there is a fair chance that you have heard of C or learned C. As a language, C is very robust and simple language but it is lagging behind the newer languages that have more features and buttons. Which is why I took it upon myself to begin a journey of adding more features via GCC Plugins.
Boilerplate
To begin with, we need a c file to contain all our plugin code. So we will begin with the basic plugin boilerplate.
// plugin.c
#include "gcc-plugin.h"
#include "plugin-version.h"
#include "diagnostic.h"
// This is required for GCC to accept the plugin
int plugin_is_GPL_compatible;
// This is where we will register the plugin hooks
int plugin_init(struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version) {
// It's good practice to check for a compatible gcc version
if (!plugin_default_version_check(version, &gcc_version)) {
return 1;
}
// A little message so that we know the plugin loads
warning(UNKNOWN_LOCATION, "Hello from %s", plugin_info->base_name);
return 0;
}
With our plugin.c boilerplate, it's time to create a Makefile so that we can compile our plugin
# Makefile
HOST_GCC=g++
TARGET_GCC=gcc
PLUGIN_SOURCE_FILES= plugin.c
# Get the GCC plugin directory for referencing later
GCCPLUGINS_DIR:= $(shell $(TARGET_GCC) -print-file-name=plugin)
CXXFLAGS+= -I$(GCCPLUGINS_DIR)/include -fPIC -fno-rtti -O2
default: embedstruct.so
embedstruct.so: $(PLUGIN_SOURCE_FILES)
$(HOST_GCC) -shared $(CXXFLAGS) $^ -o $@
.PHONY: clean
clean:
rm -f embedstruct.so
We can now build our project but it's useless to build it without a test program.
// test.c
#include <stdio.h>
int main(void) {
printf("Hello World!\n");
return 0;
}
Now to add an extra Makefile rule to build the test program.
# Makefile
...
test: embedstruct.so
$(TARGET_GCC) -fplugin=./embedstruct.so test.c -o test
...
clean:
rm -f embedstruct.so
rm -f test
Initial Test
Now that we have our boilerplate out of the way, we can test to see if our plugin compiles and loads correctly
$ make
g++ -shared -I/usr/local/lib/gcc/x86_64-pc-linux-gnu/16.0.0/plugin/include -fPIC -fno-rtti -O2 plugin.c -o embedstruct.so
$ make test
gcc -fplugin=./embedstruct.so test.c -o test
cc1: warning: Hello from embedstruct
$ ./test
Hello World!
Success! Our code compiles and runs correctly.
Adding the Attributes
Now that our foundation is out of the way and working, we can start by adding our attribute so that GCC doesn't throw an error every time we try to use it.
Step 1
Register our plugin to the attribute registration callback
int plugin_init(...) {
...
register_callback(
plugin_info->base_name, // Name of our plugin
PLUGIN_ATTRIBUTES, // The callback to hook into
register_attributes, // Our callback we will create
NULL // We don't have any user data
);
...
}
Step 2
Define an attribute_spec for our attribute
static struct attribute_spec embed_struct_attr = {
"embed_struct", // Name of attribute used in C code
0, // minimum of 0 arguments
0, // maximum of 0 arguments
true, // This attribute only applies to declarations
false, // doesn't apply to type
false, // doesn't apply to function types
false, // doesn't affect type identity
handle_embed_struct_attr, // callback to attribute processing
NULL, // we don't have any exclusions
};
Step 3
Add boilerplate attribute handling callback
static tree handle_embed_struct_attr(tree *node, tree name, tree args,
int flags, bool *no_add_attrs) {
// Shockingly, most of the code that we write
// will not be in this block
warning(UNKNOWN_LOCATION, "attribute 'embed_struct' not implemented");
return NULL_TREE;
}
Step 4
Register our attribute to GCC
static void register_attributes(void *event_data, void *data) {
// Tell GCC that we have a custom attribute
register_attribute(&embed_struct_attr);
}
Step 5
Add tree.h to the list of headers
Testing
Let's modify our test.c code so that we use our custom attribute
#include <stdio.h>
// Parent struct type
struct s1 {
int field1;
int field2;
};
// Child struct type
struct s2 {
// Our parent type with the embed_struct attribute
[[gnu::embed_struct]]
struct s1 super;
int field3;
};
int main(void) {
struct s2 test;
test.super.field1 = 0;
test.super.field2 = 1;
test.field3 = 2;
printf("super.field1: %d\n", test.super.field1);
printf("super.field2: %d\n", test.super.field2);
printf("field3: %d\n", test.field3);
return 0;
}
Now to compare before and after registering the attribute
# Before
$ make test
gcc -fplugin=./embedstruct.so test.c -o test
cc1: warning: Hello from embedstruct
test.c:13:10: warning: 'gnu::embed_struct' scoped attribute directive ignored [-Wattributes]
13 | struct s1 super;
| ^~
$ ./test
super.field1: 0
super.field2: 1
field3: 2
# After
$ make test
gcc -fplugin=./embedstruct.so test.c -o test
cc1: warning: Hello from embedstruct
test.c:13:10: warning: attribute 'embed_struct' not implemented
13 | struct s1 super;
| ^~
$ ./test
super.field1: 0
super.field2: 1
field3: 2
The difference isn't much, however the warning we get after registering is the warning that WE decided to create because we haven't finished the attribute
Finish Type Pass
To implement our new type, we need to do 4 steps.
3.
- Remove the
embedstructattribute
Step 1
Hook into the FINISH_TYPE callback. This will allow us to change the type structure.
int plugin_init(...) {
...
register_callback(
plugin_info->base_name, // Name of our plugin
PLUGIN_FINISH_TYPE, // The callback to hook into
finish_type, // Our callback we will create
NULL // We don't have any user data
);
...
}
Step 2
Check each field for the embedstruct attribute
static void finish_type(void *event_data, void *data) {
// Get the current type data
tree type = (tree)event_data;
// We don't like segmentation faults so we will check
// for NULL pointers
if (!type)
return;
// We only want this to work inside of `struct` types
if (TREE_CODE(type) != RECORD_TYPE)
return;
// Get the list of every struct field
tree *prev = &TYPE_FIELDS(type);
while (*prev) {
tree field = *prev;
// If this isn't a declared struct field then we don't
// want to test it
if (TREE_CODE(field) != FIELD_DECL) {
prev = &DECL_CHAIN(field);
continue;
}
// Check if our attribute is attached
if (lookup_attribute("embed_struct", DECL_ATTRIBUTES(field))) {
// TODO add anonymous structure at same location
}
prev = &DECL_CHAIN(field);
}
}
Step 3
Add an anonymous struct of the same type and the same location. Easiest way to do this is to copy the existing field without it's declared name and add it to the field list
static tree copy_field_anonymous(tree field, tree context) {
// Create a declaration node
tree new_field = build_decl(DECL_SOURCE_LOCATION(field), FIELD_DECL,
NULL_TREE, TREE_TYPE(field));
// Copy the field data
DECL_FIELD_BIT_OFFSET(new_field) = DECL_FIELD_BIT_OFFSET(field);
DECL_ALIGN_RAW(new_field) = DECL_ALIGN_RAW(field);
SET_DECL_OFFSET_ALIGN(new_field, DECL_OFFSET_ALIGN(field));
DECL_NOT_FLEXARRAY(new_field) = DECL_NOT_FLEXARRAY(field);
DECL_FIELD_OFFSET(new_field) = DECL_FIELD_OFFSET(field);
DECL_SIZE(new_field) = DECL_SIZE(field);
DECL_SIZE_UNIT(new_field) = DECL_SIZE_UNIT(field);
// Point to the same attributes for same effects
DECL_ATTRIBUTES(new_field) = DECL_ATTRIBUTES(field);
// Add this to the correct context (sort of similar to a parent)
DECL_CONTEXT(new_field) = context;
// Remove this from any list so we can move it wherever we want
DECL_CHAIN(new_field) = NULL_TREE;
// Let the compiler know that this was a computed declaration
DECL_ARTIFICIAL(new_field) = 1;
return new_field;
}
static void finish_type(void *event_data, void *data) {
...
if (lookup_attribute("embed_struct", DECL_ATTRIBUTES(field))) {
// Copy the field as an anonymous struct in the same context
tree anon_field = copy_field_anonymous(field, DECL_CONTEXT(field));
// Add anonymous field to the list
DECL_CHAIN(anon_field) = DECL_CHAIN(field);
DECL_CHAIN(field) = anon_field;
prev = &DECL_CHAIN(anon_field);
// TODO remove attribute
continue;
}
...
}
Step 4
While technically unnecessary, I still prefer to remove the attributes after processing.
static void remove_attribute(tree node, const char *attribute) {
// Iterate over attribute list
for (tree *attr_ptr = &DECL_ATTRIBUTES(node); *attr_ptr;) {
if (is_attribute_p(attribute, get_attribute_name(*attr_ptr))) {
// Remove attribute
*attr_ptr = TREE_CHAIN(*attr_ptr);
} else {
// Check next attribute
attr_ptr = &TREE_CHAIN(*attr_ptr);
}
}
}
static void finish_type(void *event_data, void *data) {
...
prev = &DECL_CHAIN(anon_field);
// We only need to remove from one of them because
// their attributes are linked
remove_attribute(anon_field, "embed_struct");
continue;
...
}
Step 5
Remove the unnecessary warnings
warning(UNKNOWN_LOCATION, "attribute 'embed_struct' not implemented");
warning(UNKNOWN_LOCATION, "Hello from %s", plugin_info->base_name);
Final Test
Now that we have finished our plugin, let's update our test program to check whether it works
#include <stdio.h>
// Parent struct type
struct s1 {
int field1;
int field2;
};
// Child struct type
struct s2 {
// Our parent type with the embed_struct attribute
[[gnu::embed_struct]]
struct s1 super;
int field3;
};
int main(void) {
struct s2 test;
test.super.field1 = 0;
test.super.field2 = 1;
test.field3 = 2;
printf("super.field1: %d\n", test.super.field1);
printf("super.field2: %d\n", test.super.field2);
printf("field1: %d\n", test.field1);
printf("field2: %d\n", test.field2);
printf("field3: %d\n", test.field3);
return 0;
}
Time for the big reveal
$ make test
g++ -shared -I/usr/local/lib/gcc/x86_64-pc-linux-gnu/16.0.0/plugin/include -fPIC -fno-rtti -O2 plugin.c -o embedstruct.so
gcc -fplugin=./embedstruct.so test.c -o test
$ ./test
super.field1: 0
super.field2: 1
field1: 0
field2: 1
field3: 2
As you can see, field1 is linked to super.field1, and field2 is linked to super.field2. Now you can do better OOP in C without being forced into the C++ domain.
Summary
Using GCC plugins, you can modify the language to achieve some nice features. Is it worth doing? Probably not, but programming isn't always about doing things the easy way. You can check out my GitHub repository for this project (under a different name. I should probably change it sometime to a better name, but oh well).
gcc-flattenstruct
If I get time in future, I may extend this to have a few more features and better control over the layout.
Note.
Generative AI was used as a starting block for this project however it failed to do proper tasks and the resulting code is mostly mine.
Top comments (0)