src/plugins/ctf/common: restructure subtree
[babeltrace.git] / src / plugins / ctf / common / src / metadata / tsdl / visitor-generate-ir.cpp
diff --git a/src/plugins/ctf/common/src/metadata/tsdl/visitor-generate-ir.cpp b/src/plugins/ctf/common/src/metadata/tsdl/visitor-generate-ir.cpp
new file mode 100644 (file)
index 0000000..c53c4d8
--- /dev/null
@@ -0,0 +1,4744 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * Copyright 2010 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ * Copyright 2015-2018 Philippe Proulx <philippe.proulx@efficios.com>
+ *
+ * Common Trace Format metadata visitor (generates CTF IR objects).
+ */
+
+#include <string>
+
+#include <errno.h>
+#include <glib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <babeltrace2/babeltrace.h>
+
+#define BT_COMP_LOG_SELF_COMP       (ctx->log_cfg.self_comp)
+#define BT_COMP_LOG_SELF_COMP_CLASS (ctx->log_cfg.self_comp_class)
+#define BT_LOG_OUTPUT_LEVEL         (ctx->log_cfg.log_level)
+#define BT_LOG_TAG                  "PLUGIN/CTF/META/IR-VISITOR"
+#include "logging.hpp"
+#include "logging/comp-logging.h"
+
+#include "common/assert.h"
+#include "common/common.h"
+#include "common/uuid.h"
+#include "compat/endian.h" /* IWYU pragma: keep  */
+
+#include "ast.hpp"
+#include "ctf-meta-visitors.hpp"
+#include "ctf-meta.hpp"
+#include "decoder.hpp"
+
+/* Bit value (left shift) */
+#define _BV(_val) (1 << (_val))
+
+/* Bit is set in a set of bits */
+#define _IS_SET(_set, _mask) (*(_set) & (_mask))
+
+/* Set bit in a set of bits */
+#define _SET(_set, _mask) (*(_set) |= (_mask))
+
+/* Try to push scope, or go to the `error` label */
+#define _TRY_PUSH_SCOPE_OR_GOTO_ERROR()                                                            \
+    do {                                                                                           \
+        ret = ctx_push_scope(ctx);                                                                 \
+        if (ret) {                                                                                 \
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot push scope.");                        \
+            goto error;                                                                            \
+        }                                                                                          \
+    } while (0)
+
+/* Bits for verifying existing attributes in various declarations */
+enum
+{
+    _CLOCK_NAME_SET = _BV(0),
+    _CLOCK_UUID_SET = _BV(1),
+    _CLOCK_FREQ_SET = _BV(2),
+    _CLOCK_PRECISION_SET = _BV(3),
+    _CLOCK_OFFSET_S_SET = _BV(4),
+    _CLOCK_OFFSET_SET = _BV(5),
+    _CLOCK_ABSOLUTE_SET = _BV(6),
+    _CLOCK_DESCRIPTION_SET = _BV(7),
+};
+
+enum
+{
+    _INTEGER_ALIGN_SET = _BV(0),
+    _INTEGER_SIZE_SET = _BV(1),
+    _INTEGER_BASE_SET = _BV(2),
+    _INTEGER_ENCODING_SET = _BV(3),
+    _INTEGER_BYTE_ORDER_SET = _BV(4),
+    _INTEGER_SIGNED_SET = _BV(5),
+    _INTEGER_MAP_SET = _BV(6),
+};
+
+enum
+{
+    _FLOAT_ALIGN_SET = _BV(0),
+    _FLOAT_MANT_DIG_SET = _BV(1),
+    _FLOAT_EXP_DIG_SET = _BV(2),
+    _FLOAT_BYTE_ORDER_SET = _BV(3),
+};
+
+enum
+{
+    _STRING_ENCODING_SET = _BV(0),
+};
+
+enum
+{
+    _TRACE_MINOR_SET = _BV(0),
+    _TRACE_MAJOR_SET = _BV(1),
+    _TRACE_BYTE_ORDER_SET = _BV(2),
+    _TRACE_UUID_SET = _BV(3),
+    _TRACE_PACKET_HEADER_SET = _BV(4),
+};
+
+enum
+{
+    _STREAM_ID_SET = _BV(0),
+    _STREAM_PACKET_CONTEXT_SET = _BV(1),
+    _STREAM_EVENT_HEADER_SET = _BV(2),
+    _STREAM_EVENT_CONTEXT_SET = _BV(3),
+};
+
+enum
+{
+    _EVENT_NAME_SET = _BV(0),
+    _EVENT_ID_SET = _BV(1),
+    _EVENT_MODEL_EMF_URI_SET = _BV(2),
+    _EVENT_STREAM_ID_SET = _BV(3),
+    _EVENT_LOG_LEVEL_SET = _BV(4),
+    _EVENT_CONTEXT_SET = _BV(5),
+    _EVENT_FIELDS_SET = _BV(6),
+};
+
+enum loglevel
+{
+    LOG_LEVEL_EMERG = 0,
+    LOG_LEVEL_ALERT = 1,
+    LOG_LEVEL_CRIT = 2,
+    LOG_LEVEL_ERR = 3,
+    LOG_LEVEL_WARNING = 4,
+    LOG_LEVEL_NOTICE = 5,
+    LOG_LEVEL_INFO = 6,
+    LOG_LEVEL_DEBUG_SYSTEM = 7,
+    LOG_LEVEL_DEBUG_PROGRAM = 8,
+    LOG_LEVEL_DEBUG_PROCESS = 9,
+    LOG_LEVEL_DEBUG_MODULE = 10,
+    LOG_LEVEL_DEBUG_UNIT = 11,
+    LOG_LEVEL_DEBUG_FUNCTION = 12,
+    LOG_LEVEL_DEBUG_LINE = 13,
+    LOG_LEVEL_DEBUG = 14,
+    _NR_LOGLEVELS = 15,
+};
+
+/* Prefixes of class aliases */
+#define _PREFIX_ALIAS   'a'
+#define _PREFIX_ENUM    'e'
+#define _PREFIX_STRUCT  's'
+#define _PREFIX_VARIANT 'v'
+
+/* First entry in a BT list */
+#define _BT_LIST_FIRST_ENTRY(_ptr, _class, _member) bt_list_entry((_ptr)->next, _class, _member)
+
+#define _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(_node, _attr, _entity)                                 \
+    _BT_COMP_LOGE_APPEND_CAUSE_LINENO(                                                             \
+        (_node)->lineno, "Duplicate attribute in %s: attr-name=\"%s\"", _entity, _attr)
+
+#define _BT_COMP_LOGE_NODE(_node, _msg, args...) _BT_COMP_LOGE_LINENO((_node)->lineno, _msg, ##args)
+
+#define _BT_COMP_LOGE_APPEND_CAUSE_NODE(_node, _msg, args...)                                      \
+    _BT_COMP_LOGE_APPEND_CAUSE_LINENO((_node)->lineno, _msg, ##args)
+
+#define _BT_COMP_LOGW_NODE(_node, _msg, args...) _BT_COMP_LOGW_LINENO((_node)->lineno, _msg, ##args)
+
+#define _BT_COMP_LOGT_NODE(_node, _msg, args...) _BT_COMP_LOGT_LINENO((_node)->lineno, _msg, ##args)
+
+/*
+ * Declaration scope of a visitor context. This represents a TSDL
+ * lexical scope, so that aliases and named structures, variants,
+ * and enumerations may be registered and looked up hierarchically.
+ */
+struct ctx_decl_scope
+{
+    /*
+     * Alias name to field class.
+     *
+     * GQuark -> struct ctf_field_class * (owned by this)
+     */
+    GHashTable *decl_map;
+
+    /* Parent scope; NULL if this is the root declaration scope */
+    struct ctx_decl_scope *parent_scope;
+};
+
+/*
+ * Visitor context (private).
+ */
+struct ctf_visitor_generate_ir
+{
+    struct meta_log_config log_cfg;
+
+    /* Trace IR trace class being filled (owned by this) */
+    bt_trace_class *trace_class;
+
+    /* CTF meta trace being filled (owned by this) */
+    struct ctf_trace_class *ctf_tc;
+
+    /* Current declaration scope (top of the stack) (owned by this) */
+    struct ctx_decl_scope *current_scope;
+
+    /* True if trace declaration is visited */
+    bool is_trace_visited;
+
+    /* True if this is an LTTng trace */
+    bool is_lttng;
+
+    /* Config passed by the user */
+    struct ctf_metadata_decoder_config decoder_config;
+};
+
+/*
+ * Visitor (public).
+ */
+struct ctf_visitor_generate_ir;
+
+/**
+ * Creates a new declaration scope.
+ *
+ * @param par_scope    Parent scope (NULL if creating a root scope)
+ * @returns            New declaration scope, or NULL on error
+ */
+static struct ctx_decl_scope *ctx_decl_scope_create(struct ctf_visitor_generate_ir *ctx,
+                                                    struct ctx_decl_scope *par_scope)
+{
+    struct ctx_decl_scope *scope;
+
+    scope = g_new(struct ctx_decl_scope, 1);
+    if (!scope) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Failed to allocate one declaration scope.");
+        goto end;
+    }
+
+    scope->decl_map = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+                                            (GDestroyNotify) ctf_field_class_destroy);
+    scope->parent_scope = par_scope;
+
+end:
+    return scope;
+}
+
+/**
+ * Destroys a declaration scope.
+ *
+ * This function does not destroy the parent scope.
+ *
+ * @param scope        Scope to destroy
+ */
+static void ctx_decl_scope_destroy(struct ctx_decl_scope *scope)
+{
+    if (!scope) {
+        goto end;
+    }
+
+    g_hash_table_destroy(scope->decl_map);
+    g_free(scope);
+
+end:
+    return;
+}
+
+/**
+ * Returns the GQuark of a prefixed alias.
+ *
+ * @param prefix       Prefix character
+ * @param name         Name
+ * @returns            Associated GQuark, or 0 on error
+ */
+static GQuark get_prefixed_named_quark(char prefix, const char *name)
+{
+    BT_ASSERT(name);
+    std::string prname = std::string {prefix} + name;
+    return g_quark_from_string(prname.c_str());
+}
+
+/**
+ * Looks up a prefixed class alias within a declaration scope.
+ *
+ * @param scope                Declaration scope
+ * @param prefix       Prefix character
+ * @param name         Alias name
+ * @param levels       Number of levels to dig into (-1 means infinite)
+ * @param copy         True to return a copy
+ * @returns            Declaration (owned by caller if \p copy is true),
+ *                     or NULL if not found
+ */
+static struct ctf_field_class *ctx_decl_scope_lookup_prefix_alias(struct ctx_decl_scope *scope,
+                                                                  char prefix, const char *name,
+                                                                  int levels, bool copy)
+{
+    GQuark qname = 0;
+    int cur_levels = 0;
+    struct ctf_field_class *decl = NULL;
+    struct ctx_decl_scope *cur_scope = scope;
+
+    BT_ASSERT(scope);
+    BT_ASSERT(name);
+    qname = get_prefixed_named_quark(prefix, name);
+    if (!qname) {
+        goto end;
+    }
+
+    if (levels < 0) {
+        levels = INT_MAX;
+    }
+
+    while (cur_scope && cur_levels < levels) {
+        decl = (ctf_field_class *) g_hash_table_lookup(cur_scope->decl_map,
+                                                       (gconstpointer) GUINT_TO_POINTER(qname));
+        if (decl) {
+            /* Caller's reference */
+            if (copy) {
+                decl = ctf_field_class_copy(decl);
+                BT_ASSERT(decl);
+            }
+
+            goto end;
+        }
+
+        cur_scope = cur_scope->parent_scope;
+        cur_levels++;
+    }
+
+end:
+    return decl;
+}
+
+/**
+ * Looks up a class alias within a declaration scope.
+ *
+ * @param scope                Declaration scope
+ * @param name         Alias name
+ * @param levels       Number of levels to dig into (-1 means infinite)
+ * @param copy         True to return a copy
+ * @returns            Declaration (owned by caller if \p copy is true),
+ *                     or NULL if not found
+ */
+static struct ctf_field_class *ctx_decl_scope_lookup_alias(struct ctx_decl_scope *scope,
+                                                           const char *name, int levels, bool copy)
+{
+    return ctx_decl_scope_lookup_prefix_alias(scope, _PREFIX_ALIAS, name, levels, copy);
+}
+
+/**
+ * Looks up an enumeration within a declaration scope.
+ *
+ * @param scope                Declaration scope
+ * @param name         Enumeration name
+ * @param levels       Number of levels to dig into (-1 means infinite)
+ * @param copy         True to return a copy
+ * @returns            Declaration (owned by caller if \p copy is true),
+ *                     or NULL if not found
+ */
+static struct ctf_field_class_enum *
+ctx_decl_scope_lookup_enum(struct ctx_decl_scope *scope, const char *name, int levels, bool copy)
+{
+    return ctf_field_class_as_enum(
+        ctx_decl_scope_lookup_prefix_alias(scope, _PREFIX_ENUM, name, levels, copy));
+}
+
+/**
+ * Looks up a structure within a declaration scope.
+ *
+ * @param scope                Declaration scope
+ * @param name         Structure name
+ * @param levels       Number of levels to dig into (-1 means infinite)
+ * @param copy         True to return a copy
+ * @returns            Declaration (owned by caller if \p copy is true),
+ *                     or NULL if not found
+ */
+static struct ctf_field_class_struct *
+ctx_decl_scope_lookup_struct(struct ctx_decl_scope *scope, const char *name, int levels, bool copy)
+{
+    return ctf_field_class_as_struct(
+        ctx_decl_scope_lookup_prefix_alias(scope, _PREFIX_STRUCT, name, levels, copy));
+}
+
+/**
+ * Looks up a variant within a declaration scope.
+ *
+ * @param scope                Declaration scope
+ * @param name         Variant name
+ * @param levels       Number of levels to dig into (-1 means infinite)
+ * @param copy         True to return a copy
+ * @returns            Declaration (owned by caller if \p copy is true),
+ *                     or NULL if not found
+ */
+static struct ctf_field_class_variant *
+ctx_decl_scope_lookup_variant(struct ctx_decl_scope *scope, const char *name, int levels, bool copy)
+{
+    return ctf_field_class_as_variant(
+        ctx_decl_scope_lookup_prefix_alias(scope, _PREFIX_VARIANT, name, levels, copy));
+}
+
+/**
+ * Registers a prefixed class alias within a declaration scope.
+ *
+ * @param scope                Declaration scope
+ * @param prefix       Prefix character
+ * @param name         Alias name (non-NULL)
+ * @param decl         Field class to register (copied)
+ * @returns            0 if registration went okay, negative value otherwise
+ */
+static int ctx_decl_scope_register_prefix_alias(struct ctx_decl_scope *scope, char prefix,
+                                                const char *name, struct ctf_field_class *decl)
+{
+    int ret = 0;
+    GQuark qname = 0;
+
+    BT_ASSERT(scope);
+    BT_ASSERT(name);
+    BT_ASSERT(decl);
+    qname = get_prefixed_named_quark(prefix, name);
+    if (!qname) {
+        ret = -ENOMEM;
+        goto end;
+    }
+
+    /* Make sure alias does not exist in local scope */
+    if (ctx_decl_scope_lookup_prefix_alias(scope, prefix, name, 1, false)) {
+        ret = -EEXIST;
+        goto end;
+    }
+
+    decl = ctf_field_class_copy(decl);
+    BT_ASSERT(decl);
+    g_hash_table_insert(scope->decl_map, GUINT_TO_POINTER(qname), decl);
+
+end:
+    return ret;
+}
+
+/**
+ * Registers a class alias within a declaration scope.
+ *
+ * @param scope        Declaration scope
+ * @param name Alias name (non-NULL)
+ * @param decl Field class to register (copied)
+ * @returns    0 if registration went okay, negative value otherwise
+ */
+static int ctx_decl_scope_register_alias(struct ctx_decl_scope *scope, const char *name,
+                                         struct ctf_field_class *decl)
+{
+    return ctx_decl_scope_register_prefix_alias(scope, _PREFIX_ALIAS, name, decl);
+}
+
+/**
+ * Registers an enumeration declaration within a declaration scope.
+ *
+ * @param scope        Declaration scope
+ * @param name Enumeration name (non-NULL)
+ * @param decl Enumeration field class to register (copied)
+ * @returns    0 if registration went okay, negative value otherwise
+ */
+static int ctx_decl_scope_register_enum(struct ctx_decl_scope *scope, const char *name,
+                                        struct ctf_field_class_enum *decl)
+{
+    return ctx_decl_scope_register_prefix_alias(scope, _PREFIX_ENUM, name, &decl->base.base.base);
+}
+
+/**
+ * Registers a structure declaration within a declaration scope.
+ *
+ * @param scope        Declaration scope
+ * @param name Structure name (non-NULL)
+ * @param decl Structure field class to register (copied)
+ * @returns    0 if registration went okay, negative value otherwise
+ */
+static int ctx_decl_scope_register_struct(struct ctx_decl_scope *scope, const char *name,
+                                          struct ctf_field_class_struct *decl)
+{
+    return ctx_decl_scope_register_prefix_alias(scope, _PREFIX_STRUCT, name, &decl->base);
+}
+
+/**
+ * Registers a variant declaration within a declaration scope.
+ *
+ * @param scope        Declaration scope
+ * @param name Variant name (non-NULL)
+ * @param decl Variant field class to register
+ * @returns    0 if registration went okay, negative value otherwise
+ */
+static int ctx_decl_scope_register_variant(struct ctx_decl_scope *scope, const char *name,
+                                           struct ctf_field_class_variant *decl)
+{
+    return ctx_decl_scope_register_prefix_alias(scope, _PREFIX_VARIANT, name, &decl->base);
+}
+
+/**
+ * Destroys a visitor context.
+ *
+ * @param ctx  Visitor context to destroy
+ */
+static void ctx_destroy(struct ctf_visitor_generate_ir *ctx)
+{
+    struct ctx_decl_scope *scope;
+
+    if (!ctx) {
+        goto end;
+    }
+
+    scope = ctx->current_scope;
+
+    /*
+     * Destroy all scopes, from current one to the root scope.
+     */
+    while (scope) {
+        struct ctx_decl_scope *parent_scope = scope->parent_scope;
+
+        ctx_decl_scope_destroy(scope);
+        scope = parent_scope;
+    }
+
+    bt_trace_class_put_ref(ctx->trace_class);
+
+    if (ctx->ctf_tc) {
+        ctf_trace_class_destroy(ctx->ctf_tc);
+    }
+
+    g_free(ctx);
+
+end:
+    return;
+}
+
+/**
+ * Creates a new visitor context.
+ *
+ * @param trace        Associated trace
+ * @returns    New visitor context, or NULL on error
+ */
+static struct ctf_visitor_generate_ir *
+ctx_create(const struct ctf_metadata_decoder_config *decoder_config)
+{
+    struct ctf_visitor_generate_ir *ctx = NULL;
+
+    BT_ASSERT(decoder_config);
+
+    ctx = g_new0(struct ctf_visitor_generate_ir, 1);
+    if (!ctx) {
+        BT_COMP_LOG_CUR_LVL(BT_LOG_ERROR, decoder_config->log_level, decoder_config->self_comp,
+                            "Failed to allocate one visitor context.");
+        goto error;
+    }
+
+    ctx->log_cfg.log_level = decoder_config->log_level;
+    ctx->log_cfg.self_comp = decoder_config->self_comp;
+    ctx->log_cfg.self_comp_class = decoder_config->self_comp_class;
+
+    if (decoder_config->self_comp) {
+        ctx->trace_class = bt_trace_class_create(decoder_config->self_comp);
+        if (!ctx->trace_class) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot create empty trace class.");
+            goto error;
+        }
+    }
+
+    ctx->ctf_tc = ctf_trace_class_create();
+    if (!ctx->ctf_tc) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot create CTF trace class.");
+        goto error;
+    }
+
+    /* Root declaration scope */
+    ctx->current_scope = ctx_decl_scope_create(ctx, NULL);
+    if (!ctx->current_scope) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot create declaration scope.");
+        goto error;
+    }
+
+    ctx->decoder_config = *decoder_config;
+    goto end;
+
+error:
+    ctx_destroy(ctx);
+    ctx = NULL;
+
+end:
+    return ctx;
+}
+
+/**
+ * Pushes a new declaration scope on top of a visitor context's
+ * declaration scope stack.
+ *
+ * @param ctx  Visitor context
+ * @returns    0 on success, or a negative value on error
+ */
+static int ctx_push_scope(struct ctf_visitor_generate_ir *ctx)
+{
+    int ret = 0;
+    struct ctx_decl_scope *new_scope;
+
+    BT_ASSERT(ctx);
+    new_scope = ctx_decl_scope_create(ctx, ctx->current_scope);
+    if (!new_scope) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot create declaration scope.");
+        ret = -ENOMEM;
+        goto end;
+    }
+
+    ctx->current_scope = new_scope;
+
+end:
+    return ret;
+}
+
+static void ctx_pop_scope(struct ctf_visitor_generate_ir *ctx)
+{
+    struct ctx_decl_scope *parent_scope = NULL;
+
+    BT_ASSERT(ctx);
+
+    if (!ctx->current_scope) {
+        goto end;
+    }
+
+    parent_scope = ctx->current_scope->parent_scope;
+    ctx_decl_scope_destroy(ctx->current_scope);
+    ctx->current_scope = parent_scope;
+
+end:
+    return;
+}
+
+static int visit_field_class_specifier_list(struct ctf_visitor_generate_ir *ctx,
+                                            struct ctf_node *ts_list,
+                                            struct ctf_field_class **decl);
+
+static int is_unary_string(struct bt_list_head *head)
+{
+    int ret = TRUE;
+    struct ctf_node *node;
+
+    bt_list_for_each_entry (node, head, siblings) {
+        if (node->type != NODE_UNARY_EXPRESSION) {
+            ret = FALSE;
+        }
+
+        if (node->u.unary_expression.type != UNARY_STRING) {
+            ret = FALSE;
+        }
+    }
+
+    return ret;
+}
+
+static const char *get_map_clock_name_value(struct bt_list_head *head)
+{
+    int i = 0;
+    struct ctf_node *node;
+    const char *name = NULL;
+
+    bt_list_for_each_entry (node, head, siblings) {
+        char *src_string;
+        int uexpr_type = node->u.unary_expression.type;
+        int uexpr_link = node->u.unary_expression.link;
+        int cond = node->type != NODE_UNARY_EXPRESSION || uexpr_type != UNARY_STRING ||
+                   !((uexpr_link != UNARY_LINK_UNKNOWN) ^ (i == 0));
+        if (cond) {
+            goto error;
+        }
+
+        /* Needs to be chained with . */
+        switch (node->u.unary_expression.link) {
+        case UNARY_DOTLINK:
+            break;
+        case UNARY_ARROWLINK:
+        case UNARY_DOTDOTDOT:
+            goto error;
+        default:
+            break;
+        }
+
+        src_string = node->u.unary_expression.u.string;
+
+        switch (i) {
+        case 0:
+            if (strcmp("clock", src_string)) {
+                goto error;
+            }
+            break;
+        case 1:
+            name = src_string;
+            break;
+        case 2:
+            if (strcmp("value", src_string)) {
+                goto error;
+            }
+            break;
+        default:
+            /* Extra identifier, unknown */
+            goto error;
+        }
+
+        i++;
+    }
+
+    return name;
+
+error:
+    return NULL;
+}
+
+static int is_unary_unsigned(struct bt_list_head *head)
+{
+    int ret = TRUE;
+    struct ctf_node *node;
+
+    bt_list_for_each_entry (node, head, siblings) {
+        if (node->type != NODE_UNARY_EXPRESSION) {
+            ret = FALSE;
+        }
+
+        if (node->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
+            ret = FALSE;
+        }
+    }
+
+    return ret;
+}
+
+static int get_unary_unsigned(struct ctf_visitor_generate_ir *ctx, struct bt_list_head *head,
+                              uint64_t *value)
+{
+    int i = 0;
+    int ret = 0;
+    struct ctf_node *node;
+
+    *value = 0;
+
+    if (bt_list_empty(head)) {
+        ret = -1;
+        goto end;
+    }
+
+    bt_list_for_each_entry (node, head, siblings) {
+        int uexpr_type = node->u.unary_expression.type;
+        int uexpr_link = node->u.unary_expression.link;
+        int cond = node->type != NODE_UNARY_EXPRESSION || uexpr_type != UNARY_UNSIGNED_CONSTANT ||
+                   uexpr_link != UNARY_LINK_UNKNOWN || i != 0;
+        if (cond) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Invalid constant unsigned integer.");
+            ret = -EINVAL;
+            goto end;
+        }
+
+        *value = node->u.unary_expression.u.unsigned_constant;
+        i++;
+    }
+
+end:
+    return ret;
+}
+
+static int is_unary_signed(struct bt_list_head *head)
+{
+    int ret = TRUE;
+    struct ctf_node *node;
+
+    bt_list_for_each_entry (node, head, siblings) {
+        if (node->type != NODE_UNARY_EXPRESSION) {
+            ret = FALSE;
+        }
+
+        if (node->u.unary_expression.type != UNARY_SIGNED_CONSTANT) {
+            ret = FALSE;
+        }
+    }
+
+    return ret;
+}
+
+static int get_unary_signed(struct bt_list_head *head, int64_t *value)
+{
+    int i = 0;
+    int ret = 0;
+    struct ctf_node *node;
+
+    bt_list_for_each_entry (node, head, siblings) {
+        int uexpr_type = node->u.unary_expression.type;
+        int uexpr_link = node->u.unary_expression.link;
+        int cond = node->type != NODE_UNARY_EXPRESSION ||
+                   (uexpr_type != UNARY_UNSIGNED_CONSTANT && uexpr_type != UNARY_SIGNED_CONSTANT) ||
+                   uexpr_link != UNARY_LINK_UNKNOWN || i != 0;
+        if (cond) {
+            ret = -EINVAL;
+            goto end;
+        }
+
+        switch (uexpr_type) {
+        case UNARY_UNSIGNED_CONSTANT:
+            *value = (int64_t) node->u.unary_expression.u.unsigned_constant;
+            break;
+        case UNARY_SIGNED_CONSTANT:
+            *value = node->u.unary_expression.u.signed_constant;
+            break;
+        default:
+            ret = -EINVAL;
+            goto end;
+        }
+
+        i++;
+    }
+
+end:
+    return ret;
+}
+
+static int get_unary_uuid(struct ctf_visitor_generate_ir *ctx, struct bt_list_head *head,
+                          bt_uuid_t uuid)
+{
+    return ctf_ast_get_unary_uuid(head, uuid, ctx->log_cfg.log_level, ctx->log_cfg.self_comp);
+}
+
+static int get_boolean(struct ctf_visitor_generate_ir *ctx, struct ctf_node *unary_expr)
+{
+    int ret = 0;
+
+    if (unary_expr->type != NODE_UNARY_EXPRESSION) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(unary_expr, "Expecting unary expression: node-type=%d",
+                                        unary_expr->type);
+        ret = -EINVAL;
+        goto end;
+    }
+
+    switch (unary_expr->u.unary_expression.type) {
+    case UNARY_UNSIGNED_CONSTANT:
+        ret = (unary_expr->u.unary_expression.u.unsigned_constant != 0);
+        break;
+    case UNARY_SIGNED_CONSTANT:
+        ret = (unary_expr->u.unary_expression.u.signed_constant != 0);
+        break;
+    case UNARY_STRING:
+    {
+        const char *str = unary_expr->u.unary_expression.u.string;
+
+        if (strcmp(str, "true") == 0 || strcmp(str, "TRUE") == 0) {
+            ret = TRUE;
+        } else if (strcmp(str, "false") == 0 || strcmp(str, "FALSE") == 0) {
+            ret = FALSE;
+        } else {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(unary_expr, "Unexpected boolean value: value=\"%s\"",
+                                            str);
+            ret = -EINVAL;
+            goto end;
+        }
+        break;
+    }
+    default:
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(unary_expr,
+                                        "Unexpected unary expression type: node-type=%d",
+                                        unary_expr->u.unary_expression.type);
+        ret = -EINVAL;
+        goto end;
+    }
+
+end:
+    return ret;
+}
+
+static enum ctf_byte_order byte_order_from_unary_expr(struct ctf_visitor_generate_ir *ctx,
+                                                      struct ctf_node *unary_expr)
+{
+    const char *str;
+    enum ctf_byte_order bo = CTF_BYTE_ORDER_UNKNOWN;
+
+    if (unary_expr->u.unary_expression.type != UNARY_STRING) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+            unary_expr, "\"byte_order\" attribute: expecting `be`, `le`, `network`, or `native`.");
+        goto end;
+    }
+
+    str = unary_expr->u.unary_expression.u.string;
+
+    if (strcmp(str, "be") == 0 || strcmp(str, "network") == 0) {
+        bo = CTF_BYTE_ORDER_BIG;
+    } else if (strcmp(str, "le") == 0) {
+        bo = CTF_BYTE_ORDER_LITTLE;
+    } else if (strcmp(str, "native") == 0) {
+        bo = CTF_BYTE_ORDER_DEFAULT;
+    } else {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+            unary_expr,
+            "Unexpected \"byte_order\" attribute value: "
+            "expecting `be`, `le`, `network`, or `native`: value=\"%s\"",
+            str);
+        goto end;
+    }
+
+end:
+    return bo;
+}
+
+static enum ctf_byte_order get_real_byte_order(struct ctf_visitor_generate_ir *ctx,
+                                               struct ctf_node *uexpr)
+{
+    enum ctf_byte_order bo = byte_order_from_unary_expr(ctx, uexpr);
+
+    if (bo == CTF_BYTE_ORDER_DEFAULT) {
+        bo = ctx->ctf_tc->default_byte_order;
+    }
+
+    return bo;
+}
+
+static int is_align_valid(uint64_t align)
+{
+    return (align != 0) && !(align & (align - UINT64_C(1)));
+}
+
+static int get_class_specifier_name(struct ctf_visitor_generate_ir *ctx,
+                                    struct ctf_node *cls_specifier, GString *str)
+{
+    int ret = 0;
+
+    if (cls_specifier->type != NODE_TYPE_SPECIFIER) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier, "Unexpected node type: node-type=%d",
+                                        cls_specifier->type);
+        ret = -EINVAL;
+        goto end;
+    }
+
+    switch (cls_specifier->u.field_class_specifier.type) {
+    case TYPESPEC_VOID:
+        g_string_append(str, "void");
+        break;
+    case TYPESPEC_CHAR:
+        g_string_append(str, "char");
+        break;
+    case TYPESPEC_SHORT:
+        g_string_append(str, "short");
+        break;
+    case TYPESPEC_INT:
+        g_string_append(str, "int");
+        break;
+    case TYPESPEC_LONG:
+        g_string_append(str, "long");
+        break;
+    case TYPESPEC_FLOAT:
+        g_string_append(str, "float");
+        break;
+    case TYPESPEC_DOUBLE:
+        g_string_append(str, "double");
+        break;
+    case TYPESPEC_SIGNED:
+        g_string_append(str, "signed");
+        break;
+    case TYPESPEC_UNSIGNED:
+        g_string_append(str, "unsigned");
+        break;
+    case TYPESPEC_BOOL:
+        g_string_append(str, "bool");
+        break;
+    case TYPESPEC_COMPLEX:
+        g_string_append(str, "_Complex");
+        break;
+    case TYPESPEC_IMAGINARY:
+        g_string_append(str, "_Imaginary");
+        break;
+    case TYPESPEC_CONST:
+        g_string_append(str, "const");
+        break;
+    case TYPESPEC_ID_TYPE:
+        if (cls_specifier->u.field_class_specifier.id_type) {
+            g_string_append(str, cls_specifier->u.field_class_specifier.id_type);
+        }
+        break;
+    case TYPESPEC_STRUCT:
+    {
+        struct ctf_node *node = cls_specifier->u.field_class_specifier.node;
+
+        if (!node->u._struct.name) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Unexpected empty structure field class name.");
+            ret = -EINVAL;
+            goto end;
+        }
+
+        g_string_append(str, "struct ");
+        g_string_append(str, node->u._struct.name);
+        break;
+    }
+    case TYPESPEC_VARIANT:
+    {
+        struct ctf_node *node = cls_specifier->u.field_class_specifier.node;
+
+        if (!node->u.variant.name) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Unexpected empty variant field class name.");
+            ret = -EINVAL;
+            goto end;
+        }
+
+        g_string_append(str, "variant ");
+        g_string_append(str, node->u.variant.name);
+        break;
+    }
+    case TYPESPEC_ENUM:
+    {
+        struct ctf_node *node = cls_specifier->u.field_class_specifier.node;
+
+        if (!node->u._enum.enum_id) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                node, "Unexpected empty enumeration field class (`enum`) name.");
+            ret = -EINVAL;
+            goto end;
+        }
+
+        g_string_append(str, "enum ");
+        g_string_append(str, node->u._enum.enum_id);
+        break;
+    }
+    case TYPESPEC_FLOATING_POINT:
+    case TYPESPEC_INTEGER:
+    case TYPESPEC_STRING:
+    default:
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier->u.field_class_specifier.node,
+                                        "Unexpected field class specifier type: %d",
+                                        cls_specifier->u.field_class_specifier.type);
+        ret = -EINVAL;
+        goto end;
+    }
+
+end:
+    return ret;
+}
+
+static int get_class_specifier_list_name(struct ctf_visitor_generate_ir *ctx,
+                                         struct ctf_node *cls_specifier_list, GString *str)
+{
+    int ret = 0;
+    struct ctf_node *iter;
+    int alias_item_nr = 0;
+    struct bt_list_head *head = &cls_specifier_list->u.field_class_specifier_list.head;
+
+    bt_list_for_each_entry (iter, head, siblings) {
+        if (alias_item_nr != 0) {
+            g_string_append(str, " ");
+        }
+
+        alias_item_nr++;
+        ret = get_class_specifier_name(ctx, iter, str);
+        if (ret) {
+            goto end;
+        }
+    }
+
+end:
+    return ret;
+}
+
+static GQuark create_class_alias_identifier(struct ctf_visitor_generate_ir *ctx,
+                                            struct ctf_node *cls_specifier_list,
+                                            struct ctf_node *node_field_class_declarator)
+{
+    int ret;
+    char *str_c;
+    GString *str;
+    GQuark qalias = 0;
+    struct ctf_node *iter;
+    struct bt_list_head *pointers = &node_field_class_declarator->u.field_class_declarator.pointers;
+
+    str = g_string_new("");
+    ret = get_class_specifier_list_name(ctx, cls_specifier_list, str);
+    if (ret) {
+        g_string_free(str, TRUE);
+        goto end;
+    }
+
+    bt_list_for_each_entry (iter, pointers, siblings) {
+        g_string_append(str, " *");
+
+        if (iter->u.pointer.const_qualifier) {
+            g_string_append(str, " const");
+        }
+    }
+
+    str_c = g_string_free(str, FALSE);
+    qalias = g_quark_from_string(str_c);
+    g_free(str_c);
+
+end:
+    return qalias;
+}
+
+static int visit_field_class_declarator(struct ctf_visitor_generate_ir *ctx,
+                                        struct ctf_node *cls_specifier_list, GQuark *field_name,
+                                        struct ctf_node *node_field_class_declarator,
+                                        struct ctf_field_class **field_decl,
+                                        struct ctf_field_class *nested_decl)
+{
+    /*
+     * During this whole function, nested_decl is always OURS,
+     * whereas field_decl is an output which we create, but
+     * belongs to the caller (it is moved).
+     */
+    int ret = 0;
+    *field_decl = NULL;
+
+    /* Validate field class declarator node */
+    if (node_field_class_declarator) {
+        if (node_field_class_declarator->u.field_class_declarator.type == TYPEDEC_UNKNOWN) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                node_field_class_declarator, "Unexpected field class declarator type: type=%d",
+                node_field_class_declarator->u.field_class_declarator.type);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        /* TODO: GCC bitfields not supported yet */
+        if (node_field_class_declarator->u.field_class_declarator.bitfield_len != NULL) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
+                                            "GCC bitfields are not supported as of this version.");
+            ret = -EPERM;
+            goto error;
+        }
+    }
+
+    /* Find the right nested declaration if not provided */
+    if (!nested_decl) {
+        if (node_field_class_declarator &&
+            !bt_list_empty(&node_field_class_declarator->u.field_class_declarator.pointers)) {
+            GQuark qalias;
+
+            /*
+             * If we have a pointer declarator, it HAS to
+                        * be present in the field class aliases (else
+                        * fail).
+             */
+            qalias =
+                create_class_alias_identifier(ctx, cls_specifier_list, node_field_class_declarator);
+            nested_decl = ctx_decl_scope_lookup_alias(ctx->current_scope, g_quark_to_string(qalias),
+                                                      -1, true);
+            if (!nested_decl) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
+                                                "Cannot find class alias: name=\"%s\"",
+                                                g_quark_to_string(qalias));
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (nested_decl->type == CTF_FIELD_CLASS_TYPE_INT) {
+                /* Pointer: force integer's base to 16 */
+                struct ctf_field_class_int *int_fc = ctf_field_class_as_int(nested_decl);
+
+                int_fc->disp_base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL;
+            }
+        } else {
+            ret = visit_field_class_specifier_list(ctx, cls_specifier_list, &nested_decl);
+            if (ret) {
+                BT_ASSERT(!nested_decl);
+                goto error;
+            }
+        }
+    }
+
+    BT_ASSERT(nested_decl);
+
+    if (!node_field_class_declarator) {
+        *field_decl = nested_decl;
+        nested_decl = NULL;
+        goto end;
+    }
+
+    if (node_field_class_declarator->u.field_class_declarator.type == TYPEDEC_ID) {
+        if (node_field_class_declarator->u.field_class_declarator.u.id) {
+            const char *id = node_field_class_declarator->u.field_class_declarator.u.id;
+
+            *field_name = g_quark_from_string(id);
+        } else {
+            *field_name = 0;
+        }
+
+        *field_decl = nested_decl;
+        nested_decl = NULL;
+        goto end;
+    } else {
+        struct ctf_node *first;
+        struct ctf_field_class *decl = NULL;
+        struct ctf_field_class *outer_field_decl = NULL;
+        struct bt_list_head *length =
+            &node_field_class_declarator->u.field_class_declarator.u.nested.length;
+
+        /* Create array/sequence, pass nested_decl as child */
+        if (bt_list_empty(length)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
+                                            "Expecting length field reference or value.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        first = _BT_LIST_FIRST_ENTRY(length, struct ctf_node, siblings);
+        if (first->type != NODE_UNARY_EXPRESSION) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(first, "Unexpected node type: node-type=%d",
+                                            first->type);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        switch (first->u.unary_expression.type) {
+        case UNARY_UNSIGNED_CONSTANT:
+        {
+            struct ctf_field_class_array *array_decl = NULL;
+
+            array_decl = ctf_field_class_array_create();
+            BT_ASSERT(array_decl);
+            array_decl->length = first->u.unary_expression.u.unsigned_constant;
+            array_decl->base.elem_fc = nested_decl;
+            nested_decl = NULL;
+            decl = &array_decl->base.base;
+            break;
+        }
+        case UNARY_STRING:
+        {
+            /* Lookup unsigned integer definition, create seq. */
+            struct ctf_field_class_sequence *seq_decl = NULL;
+            char *length_name = ctf_ast_concatenate_unary_strings(length);
+
+            if (!length_name) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
+                                                "Cannot concatenate unary strings.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (strncmp(length_name, "env.", 4) == 0) {
+                /* This is, in fact, an array */
+                const char *env_entry_name = &length_name[4];
+                struct ctf_trace_class_env_entry *env_entry =
+                    ctf_trace_class_borrow_env_entry_by_name(ctx->ctf_tc, env_entry_name);
+                struct ctf_field_class_array *array_decl;
+
+                if (!env_entry) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
+                                                    "Cannot find environment entry: "
+                                                    "name=\"%s\"",
+                                                    env_entry_name);
+                    ret = -EINVAL;
+                    goto error;
+                }
+
+                if (env_entry->type != CTF_TRACE_CLASS_ENV_ENTRY_TYPE_INT) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
+                                                    "Wrong environment entry type "
+                                                    "(expecting integer): "
+                                                    "name=\"%s\"",
+                                                    env_entry_name);
+                    ret = -EINVAL;
+                    goto error;
+                }
+
+                if (env_entry->value.i < 0) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
+                                                    "Invalid, negative array length: "
+                                                    "env-entry-name=\"%s\", "
+                                                    "value=%" PRId64,
+                                                    env_entry_name, env_entry->value.i);
+                    ret = -EINVAL;
+                    goto error;
+                }
+
+                array_decl = ctf_field_class_array_create();
+                BT_ASSERT(array_decl);
+                array_decl->length = (uint64_t) env_entry->value.i;
+                array_decl->base.elem_fc = nested_decl;
+                nested_decl = NULL;
+                decl = &array_decl->base.base;
+            } else {
+                seq_decl = ctf_field_class_sequence_create();
+                BT_ASSERT(seq_decl);
+                seq_decl->base.elem_fc = nested_decl;
+                nested_decl = NULL;
+                g_string_assign(seq_decl->length_ref, length_name);
+                decl = &seq_decl->base.base;
+            }
+
+            g_free(length_name);
+            break;
+        }
+        default:
+            ret = -EINVAL;
+            goto error;
+        }
+
+        BT_ASSERT(!nested_decl);
+        BT_ASSERT(decl);
+        BT_ASSERT(!*field_decl);
+
+        /*
+         * At this point, we found the next nested declaration.
+         * We currently own this (and lost the ownership of
+         * nested_decl in the meantime). Pass this next
+         * nested declaration as the content of the outer
+         * container, MOVING its ownership.
+         */
+        ret = visit_field_class_declarator(
+            ctx, cls_specifier_list, field_name,
+            node_field_class_declarator->u.field_class_declarator.u.nested.field_class_declarator,
+            &outer_field_decl, decl);
+        decl = NULL;
+        if (ret) {
+            BT_ASSERT(!outer_field_decl);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        BT_ASSERT(outer_field_decl);
+        *field_decl = outer_field_decl;
+        outer_field_decl = NULL;
+    }
+
+    BT_ASSERT(*field_decl);
+    goto end;
+
+error:
+    ctf_field_class_destroy(*field_decl);
+    *field_decl = NULL;
+
+    if (ret >= 0) {
+        ret = -1;
+    }
+
+end:
+    ctf_field_class_destroy(nested_decl);
+    nested_decl = NULL;
+    return ret;
+}
+
+static int visit_struct_decl_field(struct ctf_visitor_generate_ir *ctx,
+                                   struct ctf_field_class_struct *struct_decl,
+                                   struct ctf_node *cls_specifier_list,
+                                   struct bt_list_head *field_class_declarators)
+{
+    int ret = 0;
+    struct ctf_node *iter;
+    struct ctf_field_class *field_decl = NULL;
+
+    bt_list_for_each_entry (iter, field_class_declarators, siblings) {
+        field_decl = NULL;
+        GQuark qfield_name;
+        const char *field_name;
+
+        ret = visit_field_class_declarator(ctx, cls_specifier_list, &qfield_name, iter, &field_decl,
+                                           NULL);
+        if (ret) {
+            BT_ASSERT(!field_decl);
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
+                                            "Cannot visit field class declarator: ret=%d", ret);
+            goto error;
+        }
+
+        BT_ASSERT(field_decl);
+        field_name = g_quark_to_string(qfield_name);
+
+        /* Check if field with same name already exists */
+        if (ctf_field_class_struct_borrow_member_by_name(struct_decl, field_name)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
+                                            "Duplicate field in structure field class: "
+                                            "field-name=\"%s\"",
+                                            field_name);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        /* Add field to structure */
+        ctf_field_class_struct_append_member(struct_decl, field_name, field_decl);
+        field_decl = NULL;
+    }
+
+    return 0;
+
+error:
+    ctf_field_class_destroy(field_decl);
+    field_decl = NULL;
+    return ret;
+}
+
+static int visit_variant_decl_field(struct ctf_visitor_generate_ir *ctx,
+                                    struct ctf_field_class_variant *variant_decl,
+                                    struct ctf_node *cls_specifier_list,
+                                    struct bt_list_head *field_class_declarators)
+{
+    int ret = 0;
+    struct ctf_node *iter;
+    struct ctf_field_class *field_decl = NULL;
+
+    bt_list_for_each_entry (iter, field_class_declarators, siblings) {
+        field_decl = NULL;
+        GQuark qfield_name;
+        const char *field_name;
+
+        ret = visit_field_class_declarator(ctx, cls_specifier_list, &qfield_name, iter, &field_decl,
+                                           NULL);
+        if (ret) {
+            BT_ASSERT(!field_decl);
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
+                                            "Cannot visit field class declarator: ret=%d", ret);
+            goto error;
+        }
+
+        BT_ASSERT(field_decl);
+        field_name = g_quark_to_string(qfield_name);
+
+        /* Check if field with same name already exists */
+        if (ctf_field_class_variant_borrow_option_by_name(variant_decl, field_name)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
+                                            "Duplicate field in variant field class: "
+                                            "field-name=\"%s\"",
+                                            field_name);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        /* Add field to structure */
+        ctf_field_class_variant_append_option(variant_decl, field_name, field_decl);
+        field_decl = NULL;
+    }
+
+    return 0;
+
+error:
+    ctf_field_class_destroy(field_decl);
+    field_decl = NULL;
+    return ret;
+}
+
+static int visit_field_class_def(struct ctf_visitor_generate_ir *ctx,
+                                 struct ctf_node *cls_specifier_list,
+                                 struct bt_list_head *field_class_declarators)
+{
+    int ret = 0;
+    GQuark qidentifier;
+    struct ctf_node *iter;
+    struct ctf_field_class *class_decl = NULL;
+
+    bt_list_for_each_entry (iter, field_class_declarators, siblings) {
+        ret = visit_field_class_declarator(ctx, cls_specifier_list, &qidentifier, iter, &class_decl,
+                                           NULL);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit field class declarator: ret=%d",
+                                            ret);
+            ret = -EINVAL;
+            goto end;
+        }
+
+        /* Do not allow field class def and alias of untagged variants */
+        if (class_decl->type == CTF_FIELD_CLASS_TYPE_VARIANT) {
+            struct ctf_field_class_variant *var_fc = ctf_field_class_as_variant(class_decl);
+
+            if (var_fc->tag_path.path->len == 0) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    iter, "Type definition of untagged variant field class is not allowed.");
+                ret = -EPERM;
+                goto end;
+            }
+        }
+
+        ret = ctx_decl_scope_register_alias(ctx->current_scope, g_quark_to_string(qidentifier),
+                                            class_decl);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot register field class alias: name=\"%s\"",
+                                            g_quark_to_string(qidentifier));
+            goto end;
+        }
+    }
+
+end:
+    ctf_field_class_destroy(class_decl);
+    class_decl = NULL;
+    return ret;
+}
+
+static int visit_field_class_alias(struct ctf_visitor_generate_ir *ctx, struct ctf_node *target,
+                                   struct ctf_node *alias)
+{
+    int ret = 0;
+    GQuark qalias;
+    struct ctf_node *node;
+    GQuark qdummy_field_name;
+    struct ctf_field_class *class_decl = NULL;
+
+    /* Create target field class */
+    if (bt_list_empty(&target->u.field_class_alias_target.field_class_declarators)) {
+        node = NULL;
+    } else {
+        node = _BT_LIST_FIRST_ENTRY(&target->u.field_class_alias_target.field_class_declarators,
+                                    struct ctf_node, siblings);
+    }
+
+    ret = visit_field_class_declarator(
+        ctx, target->u.field_class_alias_target.field_class_specifier_list, &qdummy_field_name,
+        node, &class_decl, NULL);
+    if (ret) {
+        BT_ASSERT(!class_decl);
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot visit field class declarator: ret=%d", ret);
+        goto end;
+    }
+
+    /* Do not allow field class def and alias of untagged variants */
+    if (class_decl->type == CTF_FIELD_CLASS_TYPE_VARIANT) {
+        struct ctf_field_class_variant *var_fc = ctf_field_class_as_variant(class_decl);
+
+        if (var_fc->tag_path.path->len == 0) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                target, "Type definition of untagged variant field class is not allowed.");
+            ret = -EPERM;
+            goto end;
+        }
+    }
+
+    /*
+     * The semantic validator does not check whether the target is
+     * abstract or not (if it has an identifier). Check it here.
+     */
+    if (qdummy_field_name != 0) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(target, "Expecting empty identifier: id=\"%s\"",
+                                        g_quark_to_string(qdummy_field_name));
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /* Create alias identifier */
+    node = _BT_LIST_FIRST_ENTRY(&alias->u.field_class_alias_name.field_class_declarators,
+                                struct ctf_node, siblings);
+    qalias = create_class_alias_identifier(
+        ctx, alias->u.field_class_alias_name.field_class_specifier_list, node);
+    ret = ctx_decl_scope_register_alias(ctx->current_scope, g_quark_to_string(qalias), class_decl);
+    if (ret) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot register class alias: name=\"%s\"",
+                                        g_quark_to_string(qalias));
+        goto end;
+    }
+
+end:
+    ctf_field_class_destroy(class_decl);
+    class_decl = NULL;
+    return ret;
+}
+
+static int visit_struct_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *entry_node,
+                                   struct ctf_field_class_struct *struct_decl)
+{
+    int ret = 0;
+
+    switch (entry_node->type) {
+    case NODE_TYPEDEF:
+        ret = visit_field_class_def(ctx, entry_node->u.field_class_def.field_class_specifier_list,
+                                    &entry_node->u.field_class_def.field_class_declarators);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Cannot add field class found in structure field class: ret=%d", ret);
+            goto end;
+        }
+        break;
+    case NODE_TYPEALIAS:
+        ret = visit_field_class_alias(ctx, entry_node->u.field_class_alias.target,
+                                      entry_node->u.field_class_alias.alias);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Cannot add field class alias found in structure field class: ret=%d",
+                ret);
+            goto end;
+        }
+        break;
+    case NODE_STRUCT_OR_VARIANT_DECLARATION:
+        /* Field */
+        ret = visit_struct_decl_field(
+            ctx, struct_decl,
+            entry_node->u.struct_or_variant_declaration.field_class_specifier_list,
+            &entry_node->u.struct_or_variant_declaration.field_class_declarators);
+        if (ret) {
+            goto end;
+        }
+        break;
+    default:
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Unexpected node type: node-type=%d",
+                                        entry_node->type);
+        ret = -EINVAL;
+        goto end;
+    }
+
+end:
+    return ret;
+}
+
+static int visit_variant_decl_entry(struct ctf_visitor_generate_ir *ctx,
+                                    struct ctf_node *entry_node,
+                                    struct ctf_field_class_variant *variant_decl)
+{
+    int ret = 0;
+
+    switch (entry_node->type) {
+    case NODE_TYPEDEF:
+        ret = visit_field_class_def(ctx, entry_node->u.field_class_def.field_class_specifier_list,
+                                    &entry_node->u.field_class_def.field_class_declarators);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Cannot add field class found in variant field class: ret=%d", ret);
+            goto end;
+        }
+        break;
+    case NODE_TYPEALIAS:
+        ret = visit_field_class_alias(ctx, entry_node->u.field_class_alias.target,
+                                      entry_node->u.field_class_alias.alias);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Cannot add field class alias found in variant field class: ret=%d",
+                ret);
+            goto end;
+        }
+        break;
+    case NODE_STRUCT_OR_VARIANT_DECLARATION:
+        /* Field */
+        ret = visit_variant_decl_field(
+            ctx, variant_decl,
+            entry_node->u.struct_or_variant_declaration.field_class_specifier_list,
+            &entry_node->u.struct_or_variant_declaration.field_class_declarators);
+        if (ret) {
+            goto end;
+        }
+        break;
+    default:
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Unexpected node type: node-type=%d",
+                                        entry_node->type);
+        ret = -EINVAL;
+        goto end;
+    }
+
+end:
+    return ret;
+}
+
+static int visit_struct_decl(struct ctf_visitor_generate_ir *ctx, const char *name,
+                             struct bt_list_head *decl_list, int has_body,
+                             struct bt_list_head *min_align,
+                             struct ctf_field_class_struct **struct_decl)
+{
+    int ret = 0;
+
+    BT_ASSERT(struct_decl);
+    *struct_decl = NULL;
+
+    /* For named struct (without body), lookup in declaration scope */
+    if (!has_body) {
+        if (!name) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                "Bodyless structure field class: missing name.");
+            ret = -EPERM;
+            goto error;
+        }
+
+        *struct_decl = ctx_decl_scope_lookup_struct(ctx->current_scope, name, -1, true);
+        if (!*struct_decl) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                "Cannot find structure field class: name=\"struct %s\"", name);
+            ret = -EINVAL;
+            goto error;
+        }
+    } else {
+        struct ctf_node *entry_node;
+        uint64_t min_align_value = 0;
+
+        if (name) {
+            if (ctx_decl_scope_lookup_struct(ctx->current_scope, name, 1, false)) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Structure field class already declared in local scope: "
+                    "name=\"struct %s\"",
+                    name);
+                ret = -EINVAL;
+                goto error;
+            }
+        }
+
+        if (!bt_list_empty(min_align)) {
+            ret = get_unary_unsigned(ctx, min_align, &min_align_value);
+            if (ret) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Unexpected unary expression for structure field class's `align` attribute: "
+                    "ret=%d",
+                    ret);
+                goto error;
+            }
+        }
+
+        *struct_decl = ctf_field_class_struct_create();
+        BT_ASSERT(*struct_decl);
+
+        if (min_align_value != 0) {
+            (*struct_decl)->base.alignment = min_align_value;
+        }
+
+        _TRY_PUSH_SCOPE_OR_GOTO_ERROR();
+
+        bt_list_for_each_entry (entry_node, decl_list, siblings) {
+            ret = visit_struct_decl_entry(ctx, entry_node, *struct_decl);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node,
+                                                "Cannot visit structure field class entry: "
+                                                "ret=%d",
+                                                ret);
+                ctx_pop_scope(ctx);
+                goto error;
+            }
+        }
+
+        ctx_pop_scope(ctx);
+
+        if (name) {
+            ret = ctx_decl_scope_register_struct(ctx->current_scope, name, *struct_decl);
+            if (ret) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Cannot register structure field class in declaration scope: "
+                    "name=\"struct %s\", ret=%d",
+                    name, ret);
+                goto error;
+            }
+        }
+    }
+
+    return 0;
+
+error:
+    ctf_field_class_destroy(&(*struct_decl)->base);
+    *struct_decl = NULL;
+    return ret;
+}
+
+static int visit_variant_decl(struct ctf_visitor_generate_ir *ctx, const char *name,
+                              const char *tag, struct bt_list_head *decl_list, int has_body,
+                              struct ctf_field_class_variant **variant_decl)
+{
+    int ret = 0;
+    struct ctf_field_class_variant *untagged_variant_decl = NULL;
+
+    BT_ASSERT(variant_decl);
+    *variant_decl = NULL;
+
+    /* For named variant (without body), lookup in declaration scope */
+    if (!has_body) {
+        if (!name) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Bodyless variant field class: missing name.");
+            ret = -EPERM;
+            goto error;
+        }
+
+        untagged_variant_decl = ctx_decl_scope_lookup_variant(ctx->current_scope, name, -1, true);
+        if (!untagged_variant_decl) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                "Cannot find variant field class: name=\"variant %s\"", name);
+            ret = -EINVAL;
+            goto error;
+        }
+    } else {
+        struct ctf_node *entry_node;
+
+        if (name) {
+            if (ctx_decl_scope_lookup_variant(ctx->current_scope, name, 1, false)) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Variant field class already declared in local scope: "
+                    "name=\"variant %s\"",
+                    name);
+                ret = -EINVAL;
+                goto error;
+            }
+        }
+
+        untagged_variant_decl = ctf_field_class_variant_create();
+        BT_ASSERT(untagged_variant_decl);
+        _TRY_PUSH_SCOPE_OR_GOTO_ERROR();
+
+        bt_list_for_each_entry (entry_node, decl_list, siblings) {
+            ret = visit_variant_decl_entry(ctx, entry_node, untagged_variant_decl);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node,
+                                                "Cannot visit variant field class entry: "
+                                                "ret=%d",
+                                                ret);
+                ctx_pop_scope(ctx);
+                goto error;
+            }
+        }
+
+        ctx_pop_scope(ctx);
+
+        if (name) {
+            ret = ctx_decl_scope_register_variant(ctx->current_scope, name, untagged_variant_decl);
+            if (ret) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Cannot register variant field class in declaration scope: "
+                    "name=\"variant %s\", ret=%d",
+                    name, ret);
+                goto error;
+            }
+        }
+    }
+
+    /*
+     * If tagged, create tagged variant and return; otherwise
+     * return untagged variant.
+     */
+    if (!tag) {
+        *variant_decl = untagged_variant_decl;
+        untagged_variant_decl = NULL;
+    } else {
+        /*
+         * At this point, we have a fresh untagged variant; nobody
+         * else owns it. Set its tag now.
+         */
+        g_string_assign(untagged_variant_decl->tag_ref, tag);
+        *variant_decl = untagged_variant_decl;
+        untagged_variant_decl = NULL;
+    }
+
+    BT_ASSERT(!untagged_variant_decl);
+    BT_ASSERT(*variant_decl);
+    return 0;
+
+error:
+    ctf_field_class_destroy(&untagged_variant_decl->base);
+    untagged_variant_decl = NULL;
+    ctf_field_class_destroy(&(*variant_decl)->base);
+    *variant_decl = NULL;
+    return ret;
+}
+
+struct uori
+{
+    bool is_signed;
+    union
+    {
+        uint64_t u;
+        uint64_t i;
+    } value;
+};
+
+static int visit_enum_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *enumerator,
+                                 struct ctf_field_class_enum *enum_decl, struct uori *last)
+{
+    int ret = 0;
+    int nr_vals = 0;
+    struct ctf_node *iter;
+    struct uori start = {
+        .is_signed = false,
+        .value =
+            {
+                .u = 0,
+            },
+    };
+    struct uori end = {
+        .is_signed = false,
+        .value =
+            {
+                .u = 0,
+            },
+    };
+    const char *label = enumerator->u.enumerator.id;
+    struct bt_list_head *values = &enumerator->u.enumerator.values;
+
+    bt_list_for_each_entry (iter, values, siblings) {
+        struct uori *target;
+
+        if (iter->type != NODE_UNARY_EXPRESSION) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
+                                            "Wrong expression for enumeration field class label: "
+                                            "node-type=%d, label=\"%s\"",
+                                            iter->type, label);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (nr_vals == 0) {
+            target = &start;
+        } else {
+            target = &end;
+        }
+
+        switch (iter->u.unary_expression.type) {
+        case UNARY_SIGNED_CONSTANT:
+            target->is_signed = true;
+            target->value.i = iter->u.unary_expression.u.signed_constant;
+            break;
+        case UNARY_UNSIGNED_CONSTANT:
+            target->is_signed = false;
+            target->value.u = iter->u.unary_expression.u.unsigned_constant;
+            break;
+        default:
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
+                                            "Invalid enumeration field class entry: "
+                                            "expecting constant signed or unsigned integer: "
+                                            "node-type=%d, label=\"%s\"",
+                                            iter->u.unary_expression.type, label);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (nr_vals > 1) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                iter, "Invalid enumeration field class entry: label=\"%s\"", label);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        nr_vals++;
+    }
+
+    if (nr_vals == 0) {
+        start = *last;
+    }
+
+    if (nr_vals <= 1) {
+        end = start;
+    }
+
+    if (end.is_signed) {
+        last->value.i = end.value.i + 1;
+    } else {
+        last->value.u = end.value.u + 1;
+    }
+
+    ctf_field_class_enum_map_range(enum_decl, label, start.value.u, end.value.u);
+    return 0;
+
+error:
+    return ret;
+}
+
+static int visit_enum_decl(struct ctf_visitor_generate_ir *ctx, const char *name,
+                           struct ctf_node *container_cls, struct bt_list_head *enumerator_list,
+                           int has_body, struct ctf_field_class_enum **enum_decl)
+{
+    int ret = 0;
+    GQuark qdummy_id;
+    struct ctf_field_class_int *integer_decl = NULL;
+
+    BT_ASSERT(enum_decl);
+    *enum_decl = NULL;
+
+    /* For named enum (without body), lookup in declaration scope */
+    if (!has_body) {
+        if (!name) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                "Bodyless enumeration field class: missing name.");
+            ret = -EPERM;
+            goto error;
+        }
+
+        *enum_decl = ctx_decl_scope_lookup_enum(ctx->current_scope, name, -1, true);
+        if (!*enum_decl) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot find enumeration field class: "
+                                                     "name=\"enum %s\"",
+                                                     name);
+            ret = -EINVAL;
+            goto error;
+        }
+    } else {
+        struct ctf_node *iter;
+        struct uori last_value = {
+            .is_signed = false,
+            .value =
+                {
+                    .u = 0,
+                },
+        };
+
+        if (name) {
+            if (ctx_decl_scope_lookup_enum(ctx->current_scope, name, 1, false)) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Enumeration field class already declared in local scope: "
+                    "name=\"enum %s\"",
+                    name);
+                ret = -EINVAL;
+                goto error;
+            }
+        }
+
+        if (!container_cls) {
+            integer_decl = ctf_field_class_as_int(
+                ctx_decl_scope_lookup_alias(ctx->current_scope, "int", -1, true));
+            if (!integer_decl) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Cannot find implicit `int` field class alias for enumeration field class.");
+                ret = -EINVAL;
+                goto error;
+            }
+        } else {
+            ctf_field_class *decl;
+
+            ret = visit_field_class_declarator(ctx, container_cls, &qdummy_id, NULL, &decl, NULL);
+            if (ret) {
+                BT_ASSERT(!decl);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            integer_decl = ctf_field_class_as_int(decl);
+        }
+
+        BT_ASSERT(integer_decl);
+
+        if (integer_decl->base.base.type != CTF_FIELD_CLASS_TYPE_INT) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                "Container field class for enumeration field class is not an integer field class: "
+                "fc-type=%d",
+                integer_decl->base.base.type);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        *enum_decl = ctf_field_class_enum_create();
+        BT_ASSERT(*enum_decl);
+        (*enum_decl)->base.base.base.alignment = integer_decl->base.base.alignment;
+        ctf_field_class_int_copy_content(&(*enum_decl)->base, integer_decl);
+        last_value.is_signed = (*enum_decl)->base.is_signed;
+
+        bt_list_for_each_entry (iter, enumerator_list, siblings) {
+            ret = visit_enum_decl_entry(ctx, iter, *enum_decl, &last_value);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
+                                                "Cannot visit enumeration field class entry: "
+                                                "ret=%d",
+                                                ret);
+                goto error;
+            }
+        }
+
+        if (name) {
+            ret = ctx_decl_scope_register_enum(ctx->current_scope, name, *enum_decl);
+            if (ret) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Cannot register enumeration field class in declaration scope: "
+                    "ret=%d",
+                    ret);
+                goto error;
+            }
+        }
+    }
+
+    goto end;
+
+error:
+    ctf_field_class_destroy(&(*enum_decl)->base.base.base);
+    *enum_decl = NULL;
+
+end:
+    ctf_field_class_destroy(&integer_decl->base.base);
+    integer_decl = NULL;
+    return ret;
+}
+
+static int visit_field_class_specifier(struct ctf_visitor_generate_ir *ctx,
+                                       struct ctf_node *cls_specifier_list,
+                                       struct ctf_field_class **decl)
+{
+    int ret = 0;
+    GString *str = NULL;
+
+    *decl = NULL;
+    str = g_string_new("");
+    ret = get_class_specifier_list_name(ctx, cls_specifier_list, str);
+    if (ret) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+            cls_specifier_list, "Cannot get field class specifier list's name: ret=%d", ret);
+        goto error;
+    }
+
+    *decl = ctx_decl_scope_lookup_alias(ctx->current_scope, str->str, -1, true);
+    if (!*decl) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
+                                        "Cannot find field class alias: name=\"%s\"", str->str);
+        ret = -EINVAL;
+        goto error;
+    }
+
+    goto end;
+
+error:
+    ctf_field_class_destroy(*decl);
+    *decl = NULL;
+
+end:
+    if (str) {
+        g_string_free(str, TRUE);
+    }
+
+    return ret;
+}
+
+static int visit_integer_decl(struct ctf_visitor_generate_ir *ctx, struct bt_list_head *expressions,
+                              struct ctf_field_class_int **integer_decl)
+{
+    int set = 0;
+    int ret = 0;
+    int signedness = 0;
+    struct ctf_node *expression;
+    uint64_t alignment = 0, size = 0;
+    struct ctf_clock_class *mapped_clock_class = NULL;
+    enum ctf_encoding encoding = CTF_ENCODING_NONE;
+    bt_field_class_integer_preferred_display_base base =
+        BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL;
+    enum ctf_byte_order byte_order = ctx->ctf_tc->default_byte_order;
+
+    *integer_decl = NULL;
+
+    bt_list_for_each_entry (expression, expressions, siblings) {
+        struct ctf_node *left, *right;
+
+        left = _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.left, struct ctf_node, siblings);
+        right =
+            _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.right, struct ctf_node, siblings);
+
+        if (left->u.unary_expression.type != UNARY_STRING) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(left, "Unexpected unary expression type: type=%d",
+                                            left->u.unary_expression.type);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (strcmp(left->u.unary_expression.u.string, "signed") == 0) {
+            if (_IS_SET(&set, _INTEGER_SIGNED_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "signed", "integer field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            signedness = get_boolean(ctx, right);
+            if (signedness < 0) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid boolean value for integer field class's `signed` attribute: "
+                    "ret=%d",
+                    ret);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(&set, _INTEGER_SIGNED_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "byte_order") == 0) {
+            if (_IS_SET(&set, _INTEGER_BYTE_ORDER_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "byte_order", "integer field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            byte_order = get_real_byte_order(ctx, right);
+            if (byte_order == CTF_BYTE_ORDER_UNKNOWN) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `byte_order` attribute in integer field class: "
+                    "ret=%d",
+                    ret);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(&set, _INTEGER_BYTE_ORDER_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "size") == 0) {
+            if (_IS_SET(&set, _INTEGER_SIZE_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "size", "integer field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
+                                                "Invalid `size` attribute in integer field class: "
+                                                "expecting unsigned constant integer: "
+                                                "node-type=%d",
+                                                right->u.unary_expression.type);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            size = right->u.unary_expression.u.unsigned_constant;
+            if (size == 0) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
+                                                "Invalid `size` attribute in integer field class: "
+                                                "expecting positive constant integer: "
+                                                "size=%" PRIu64,
+                                                size);
+                ret = -EINVAL;
+                goto error;
+            } else if (size > 64) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `size` attribute in integer field class: "
+                    "integer fields over 64 bits are not supported as of this version: "
+                    "size=%" PRIu64,
+                    size);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(&set, _INTEGER_SIZE_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "align") == 0) {
+            if (_IS_SET(&set, _INTEGER_ALIGN_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "align", "integer field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
+                                                "Invalid `align` attribute in integer field class: "
+                                                "expecting unsigned constant integer: "
+                                                "node-type=%d",
+                                                right->u.unary_expression.type);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            alignment = right->u.unary_expression.u.unsigned_constant;
+            if (!is_align_valid(alignment)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
+                                                "Invalid `align` attribute in integer field class: "
+                                                "expecting power of two: "
+                                                "align=%" PRIu64,
+                                                alignment);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(&set, _INTEGER_ALIGN_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "base") == 0) {
+            if (_IS_SET(&set, _INTEGER_BASE_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "base", "integer field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            switch (right->u.unary_expression.type) {
+            case UNARY_UNSIGNED_CONSTANT:
+            {
+                uint64_t constant = right->u.unary_expression.u.unsigned_constant;
+
+                switch (constant) {
+                case 2:
+                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_BINARY;
+                    break;
+                case 8:
+                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_OCTAL;
+                    break;
+                case 10:
+                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL;
+                    break;
+                case 16:
+                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL;
+                    break;
+                default:
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                        right,
+                        "Invalid `base` attribute in integer field class: "
+                        "base=%" PRIu64,
+                        right->u.unary_expression.u.unsigned_constant);
+                    ret = -EINVAL;
+                    goto error;
+                }
+                break;
+            }
+            case UNARY_STRING:
+            {
+                char *s_right =
+                    ctf_ast_concatenate_unary_strings(&expression->u.ctf_expression.right);
+                if (!s_right) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                        right,
+                        "Unexpected unary expression for integer field class's `base` attribute.");
+                    ret = -EINVAL;
+                    goto error;
+                }
+
+                if (strcmp(s_right, "decimal") == 0 || strcmp(s_right, "dec") == 0 ||
+                    strcmp(s_right, "d") == 0 || strcmp(s_right, "i") == 0 ||
+                    strcmp(s_right, "u") == 0) {
+                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL;
+                } else if (strcmp(s_right, "hexadecimal") == 0 || strcmp(s_right, "hex") == 0 ||
+                           strcmp(s_right, "x") == 0 || strcmp(s_right, "X") == 0 ||
+                           strcmp(s_right, "p") == 0) {
+                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL;
+                } else if (strcmp(s_right, "octal") == 0 || strcmp(s_right, "oct") == 0 ||
+                           strcmp(s_right, "o") == 0) {
+                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_OCTAL;
+                } else if (strcmp(s_right, "binary") == 0 || strcmp(s_right, "b") == 0) {
+                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_BINARY;
+                } else {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                        right,
+                        "Unexpected unary expression for integer field class's `base` attribute: "
+                        "base=\"%s\"",
+                        s_right);
+                    g_free(s_right);
+                    ret = -EINVAL;
+                    goto error;
+                }
+
+                g_free(s_right);
+                break;
+            }
+            default:
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right, "Invalid `base` attribute in integer field class: "
+                           "expecting unsigned constant integer or unary string.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(&set, _INTEGER_BASE_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "encoding") == 0) {
+            char *s_right;
+
+            if (_IS_SET(&set, _INTEGER_ENCODING_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "encoding", "integer field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            if (right->u.unary_expression.type != UNARY_STRING) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right, "Invalid `encoding` attribute in integer field class: "
+                           "expecting unary string.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            s_right = ctf_ast_concatenate_unary_strings(&expression->u.ctf_expression.right);
+            if (!s_right) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Unexpected unary expression for integer field class's `encoding` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (strcmp(s_right, "UTF8") == 0 || strcmp(s_right, "utf8") == 0 ||
+                strcmp(s_right, "utf-8") == 0 || strcmp(s_right, "UTF-8") == 0 ||
+                strcmp(s_right, "ASCII") == 0 || strcmp(s_right, "ascii") == 0) {
+                encoding = CTF_ENCODING_UTF8;
+            } else if (strcmp(s_right, "none") == 0) {
+                encoding = CTF_ENCODING_NONE;
+            } else {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `encoding` attribute in integer field class: "
+                    "unknown encoding: encoding=\"%s\"",
+                    s_right);
+                g_free(s_right);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            g_free(s_right);
+            _SET(&set, _INTEGER_ENCODING_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "map") == 0) {
+            const char *clock_name;
+
+            if (_IS_SET(&set, _INTEGER_MAP_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "map", "integer field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            if (right->u.unary_expression.type != UNARY_STRING) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
+                                                "Invalid `map` attribute in integer field class: "
+                                                "expecting unary string.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            clock_name = get_map_clock_name_value(&expression->u.ctf_expression.right);
+            if (!clock_name) {
+                char *s_right =
+                    ctf_ast_concatenate_unary_strings(&expression->u.ctf_expression.right);
+
+                if (!s_right) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                        right,
+                        "Unexpected unary expression for integer field class's `map` attribute.");
+                    ret = -EINVAL;
+                    goto error;
+                }
+
+                _BT_COMP_LOGE_NODE(right,
+                                   "Invalid `map` attribute in integer field class: "
+                                   "cannot find clock class at this point: name=\"%s\"",
+                                   s_right);
+                _SET(&set, _INTEGER_MAP_SET);
+                g_free(s_right);
+                continue;
+            }
+
+            mapped_clock_class =
+                ctf_trace_class_borrow_clock_class_by_name(ctx->ctf_tc, clock_name);
+            if (!mapped_clock_class) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `map` attribute in integer field class: "
+                    "cannot find clock class at this point: name=\"%s\"",
+                    clock_name);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(&set, _INTEGER_MAP_SET);
+        } else {
+            _BT_COMP_LOGW_NODE(left,
+                               "Unknown attribute in integer field class: "
+                               "attr-name=\"%s\"",
+                               left->u.unary_expression.u.string);
+        }
+    }
+
+    if (!_IS_SET(&set, _INTEGER_SIZE_SET)) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+            "Missing `size` attribute in integer field class.");
+        ret = -EPERM;
+        goto error;
+    }
+
+    if (!_IS_SET(&set, _INTEGER_ALIGN_SET)) {
+        if (size % CHAR_BIT) {
+            /* Bit-packed alignment */
+            alignment = 1;
+        } else {
+            /* Byte-packed alignment */
+            alignment = CHAR_BIT;
+        }
+    }
+
+    *integer_decl = ctf_field_class_int_create();
+    BT_ASSERT(*integer_decl);
+    (*integer_decl)->base.base.alignment = alignment;
+    (*integer_decl)->base.byte_order = byte_order;
+    (*integer_decl)->base.size = size;
+    (*integer_decl)->is_signed = (signedness > 0);
+    (*integer_decl)->disp_base = base;
+    (*integer_decl)->encoding = encoding;
+    (*integer_decl)->mapped_clock_class = mapped_clock_class;
+    return 0;
+
+error:
+    ctf_field_class_destroy(&(*integer_decl)->base.base);
+    *integer_decl = NULL;
+    return ret;
+}
+
+static int visit_floating_point_number_decl(struct ctf_visitor_generate_ir *ctx,
+                                            struct bt_list_head *expressions,
+                                            struct ctf_field_class_float **float_decl)
+{
+    int set = 0;
+    int ret = 0;
+    struct ctf_node *expression;
+    uint64_t alignment = 1, exp_dig = 0, mant_dig = 0;
+    enum ctf_byte_order byte_order = ctx->ctf_tc->default_byte_order;
+
+    *float_decl = NULL;
+
+    bt_list_for_each_entry (expression, expressions, siblings) {
+        struct ctf_node *left, *right;
+
+        left = _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.left, struct ctf_node, siblings);
+        right =
+            _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.right, struct ctf_node, siblings);
+
+        if (left->u.unary_expression.type != UNARY_STRING) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(left, "Unexpected unary expression type: type=%d",
+                                            left->u.unary_expression.type);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (strcmp(left->u.unary_expression.u.string, "byte_order") == 0) {
+            if (_IS_SET(&set, _FLOAT_BYTE_ORDER_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "byte_order",
+                                                    "floating point number field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            byte_order = get_real_byte_order(ctx, right);
+            if (byte_order == CTF_BYTE_ORDER_UNKNOWN) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `byte_order` attribute in floating point number field class: "
+                    "ret=%d",
+                    ret);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(&set, _FLOAT_BYTE_ORDER_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "exp_dig") == 0) {
+            if (_IS_SET(&set, _FLOAT_EXP_DIG_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "exp_dig",
+                                                    "floating point number field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `exp_dig` attribute in floating point number field class: "
+                    "expecting unsigned constant integer: "
+                    "node-type=%d",
+                    right->u.unary_expression.type);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            exp_dig = right->u.unary_expression.u.unsigned_constant;
+            _SET(&set, _FLOAT_EXP_DIG_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "mant_dig") == 0) {
+            if (_IS_SET(&set, _FLOAT_MANT_DIG_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "mant_dig",
+                                                    "floating point number field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `mant_dig` attribute in floating point number field class: "
+                    "expecting unsigned constant integer: "
+                    "node-type=%d",
+                    right->u.unary_expression.type);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            mant_dig = right->u.unary_expression.u.unsigned_constant;
+            _SET(&set, _FLOAT_MANT_DIG_SET);
+        } else if (strcmp(left->u.unary_expression.u.string, "align") == 0) {
+            if (_IS_SET(&set, _FLOAT_ALIGN_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "align",
+                                                    "floating point number field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `align` attribute in floating point number field class: "
+                    "expecting unsigned constant integer: "
+                    "node-type=%d",
+                    right->u.unary_expression.type);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            alignment = right->u.unary_expression.u.unsigned_constant;
+
+            if (!is_align_valid(alignment)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `align` attribute in floating point number field class: "
+                    "expecting power of two: "
+                    "align=%" PRIu64,
+                    alignment);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(&set, _FLOAT_ALIGN_SET);
+        } else {
+            _BT_COMP_LOGW_NODE(left,
+                               "Unknown attribute in floating point number field class: "
+                               "attr-name=\"%s\"",
+                               left->u.unary_expression.u.string);
+        }
+    }
+
+    if (!_IS_SET(&set, _FLOAT_MANT_DIG_SET)) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+            "Missing `mant_dig` attribute in floating point number field class.");
+        ret = -EPERM;
+        goto error;
+    }
+
+    if (!_IS_SET(&set, _FLOAT_EXP_DIG_SET)) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+            "Missing `exp_dig` attribute in floating point number field class.");
+        ret = -EPERM;
+        goto error;
+    }
+
+    if (mant_dig != 24 && mant_dig != 53) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("`mant_dig` attribute: expecting 24 or 53.");
+        ret = -EPERM;
+        goto error;
+    }
+
+    if (mant_dig == 24 && exp_dig != 8) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+            "`exp_dig` attribute: expecting 8 because `mant_dig` is 24.");
+        ret = -EPERM;
+        goto error;
+    }
+
+    if (mant_dig == 53 && exp_dig != 11) {
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+            "`exp_dig` attribute: expecting 11 because `mant_dig` is 53.");
+        ret = -EPERM;
+        goto error;
+    }
+
+    if (!_IS_SET(&set, _INTEGER_ALIGN_SET)) {
+        if ((mant_dig + exp_dig) % CHAR_BIT) {
+            /* Bit-packed alignment */
+            alignment = 1;
+        } else {
+            /* Byte-packed alignment */
+            alignment = CHAR_BIT;
+        }
+    }
+
+    *float_decl = ctf_field_class_float_create();
+    BT_ASSERT(*float_decl);
+    (*float_decl)->base.base.alignment = alignment;
+    (*float_decl)->base.byte_order = byte_order;
+    (*float_decl)->base.size = mant_dig + exp_dig;
+    return 0;
+
+error:
+    ctf_field_class_destroy(&(*float_decl)->base.base);
+    *float_decl = NULL;
+    return ret;
+}
+
+static int visit_string_decl(struct ctf_visitor_generate_ir *ctx, struct bt_list_head *expressions,
+                             struct ctf_field_class_string **string_decl)
+{
+    int set = 0;
+    int ret = 0;
+    struct ctf_node *expression;
+    enum ctf_encoding encoding = CTF_ENCODING_UTF8;
+
+    *string_decl = NULL;
+
+    bt_list_for_each_entry (expression, expressions, siblings) {
+        struct ctf_node *left, *right;
+
+        left = _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.left, struct ctf_node, siblings);
+        right =
+            _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.right, struct ctf_node, siblings);
+
+        if (left->u.unary_expression.type != UNARY_STRING) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(left, "Unexpected unary expression type: type=%d",
+                                            left->u.unary_expression.type);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (strcmp(left->u.unary_expression.u.string, "encoding") == 0) {
+            char *s_right;
+
+            if (_IS_SET(&set, _STRING_ENCODING_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "encoding", "string field class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            if (right->u.unary_expression.type != UNARY_STRING) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right, "Invalid `encoding` attribute in string field class: "
+                           "expecting unary string.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            s_right = ctf_ast_concatenate_unary_strings(&expression->u.ctf_expression.right);
+            if (!s_right) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Unexpected unary expression for string field class's `encoding` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (strcmp(s_right, "UTF8") == 0 || strcmp(s_right, "utf8") == 0 ||
+                strcmp(s_right, "utf-8") == 0 || strcmp(s_right, "UTF-8") == 0 ||
+                strcmp(s_right, "ASCII") == 0 || strcmp(s_right, "ascii") == 0) {
+                encoding = CTF_ENCODING_UTF8;
+            } else if (strcmp(s_right, "none") == 0) {
+                encoding = CTF_ENCODING_NONE;
+            } else {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    right,
+                    "Invalid `encoding` attribute in string field class: "
+                    "unknown encoding: encoding=\"%s\"",
+                    s_right);
+                g_free(s_right);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            g_free(s_right);
+            _SET(&set, _STRING_ENCODING_SET);
+        } else {
+            _BT_COMP_LOGW_NODE(left,
+                               "Unknown attribute in string field class: "
+                               "attr-name=\"%s\"",
+                               left->u.unary_expression.u.string);
+        }
+    }
+
+    *string_decl = ctf_field_class_string_create();
+    BT_ASSERT(*string_decl);
+    (*string_decl)->encoding = encoding;
+    return 0;
+
+error:
+    ctf_field_class_destroy(&(*string_decl)->base);
+    *string_decl = NULL;
+    return ret;
+}
+
+static int visit_field_class_specifier_list(struct ctf_visitor_generate_ir *ctx,
+                                            struct ctf_node *ts_list, struct ctf_field_class **decl)
+{
+    int ret = 0;
+    struct ctf_node *first, *node;
+
+    *decl = NULL;
+
+    if (ts_list->type != NODE_TYPE_SPECIFIER_LIST) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(ts_list, "Unexpected node type: node-type=%d",
+                                        ts_list->type);
+        ret = -EINVAL;
+        goto error;
+    }
+
+    first = _BT_LIST_FIRST_ENTRY(&ts_list->u.field_class_specifier_list.head, struct ctf_node,
+                                 siblings);
+    if (first->type != NODE_TYPE_SPECIFIER) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(first, "Unexpected node type: node-type=%d", first->type);
+        ret = -EINVAL;
+        goto error;
+    }
+
+    node = first->u.field_class_specifier.node;
+
+    switch (first->u.field_class_specifier.type) {
+    case TYPESPEC_INTEGER:
+    {
+        ctf_field_class_int *int_decl;
+
+        ret = visit_integer_decl(ctx, &node->u.integer.expressions, &int_decl);
+        if (ret) {
+            BT_ASSERT(!int_decl);
+            goto error;
+        }
+
+        *decl = &int_decl->base.base;
+        break;
+    }
+    case TYPESPEC_FLOATING_POINT:
+    {
+        ctf_field_class_float *float_decl;
+
+        ret =
+            visit_floating_point_number_decl(ctx, &node->u.floating_point.expressions, &float_decl);
+        if (ret) {
+            BT_ASSERT(!float_decl);
+            goto error;
+        }
+
+        *decl = &float_decl->base.base;
+        break;
+    }
+    case TYPESPEC_STRING:
+    {
+        ctf_field_class_string *string_decl;
+
+        ret = visit_string_decl(ctx, &node->u.string.expressions, &string_decl);
+        if (ret) {
+            BT_ASSERT(!string_decl);
+            goto error;
+        }
+
+        *decl = &string_decl->base;
+        break;
+    }
+    case TYPESPEC_STRUCT:
+    {
+        ctf_field_class_struct *struct_decl;
+
+        ret = visit_struct_decl(ctx, node->u._struct.name, &node->u._struct.declaration_list,
+                                node->u._struct.has_body, &node->u._struct.min_align, &struct_decl);
+        if (ret) {
+            BT_ASSERT(!struct_decl);
+            goto error;
+        }
+
+        *decl = &struct_decl->base;
+        break;
+    }
+    case TYPESPEC_VARIANT:
+    {
+        ctf_field_class_variant *variant_decl;
+
+        ret = visit_variant_decl(ctx, node->u.variant.name, node->u.variant.choice,
+                                 &node->u.variant.declaration_list, node->u.variant.has_body,
+                                 &variant_decl);
+        if (ret) {
+            BT_ASSERT(!variant_decl);
+            goto error;
+        }
+
+        *decl = &variant_decl->base;
+        break;
+    }
+    case TYPESPEC_ENUM:
+    {
+        ctf_field_class_enum *enum_decl;
+
+        ret = visit_enum_decl(ctx, node->u._enum.enum_id, node->u._enum.container_field_class,
+                              &node->u._enum.enumerator_list, node->u._enum.has_body, &enum_decl);
+        if (ret) {
+            BT_ASSERT(!enum_decl);
+            goto error;
+        }
+
+        *decl = &enum_decl->base.base.base;
+        break;
+    }
+    case TYPESPEC_VOID:
+    case TYPESPEC_CHAR:
+    case TYPESPEC_SHORT:
+    case TYPESPEC_INT:
+    case TYPESPEC_LONG:
+    case TYPESPEC_FLOAT:
+    case TYPESPEC_DOUBLE:
+    case TYPESPEC_SIGNED:
+    case TYPESPEC_UNSIGNED:
+    case TYPESPEC_BOOL:
+    case TYPESPEC_COMPLEX:
+    case TYPESPEC_IMAGINARY:
+    case TYPESPEC_CONST:
+    case TYPESPEC_ID_TYPE:
+        ret = visit_field_class_specifier(ctx, ts_list, decl);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(first, "Cannot visit field class specifier: ret=%d",
+                                            ret);
+            BT_ASSERT(!*decl);
+            goto error;
+        }
+        break;
+    default:
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(first,
+                                        "Unexpected field class specifier type: node-type=%d",
+                                        first->u.field_class_specifier.type);
+        ret = -EINVAL;
+        goto error;
+    }
+
+    BT_ASSERT(*decl);
+    return 0;
+
+error:
+    ctf_field_class_destroy(*decl);
+    *decl = NULL;
+    return ret;
+}
+
+static int visit_event_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node,
+                                  struct ctf_event_class *event_class, uint64_t *stream_id,
+                                  int *set)
+{
+    int ret = 0;
+    char *left = NULL;
+
+    switch (node->type) {
+    case NODE_TYPEDEF:
+        ret = visit_field_class_def(ctx, node->u.field_class_def.field_class_specifier_list,
+                                    &node->u.field_class_def.field_class_declarators);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot add field class found in event class.");
+            goto error;
+        }
+        break;
+    case NODE_TYPEALIAS:
+        ret = visit_field_class_alias(ctx, node->u.field_class_alias.target,
+                                      node->u.field_class_alias.alias);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                            "Cannot add field class alias found in event class.");
+            goto error;
+        }
+        break;
+    case NODE_CTF_EXPRESSION:
+    {
+        left = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.left);
+        if (!left) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot concatenate unary strings.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (strcmp(left, "name") == 0) {
+            /* This is already known at this stage */
+            if (_IS_SET(set, _EVENT_NAME_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "name", "event class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            _SET(set, _EVENT_NAME_SET);
+        } else if (strcmp(left, "id") == 0) {
+            int64_t id = -1;
+
+            if (_IS_SET(set, _EVENT_ID_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "id", "event class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, (uint64_t *) &id);
+            /* Only read "id" if get_unary_unsigned() succeeded. */
+            if (ret || (!ret && id < 0)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Unexpected unary expression for event class's `id` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            event_class->id = id;
+            _SET(set, _EVENT_ID_SET);
+        } else if (strcmp(left, "stream_id") == 0) {
+            if (_IS_SET(set, _EVENT_STREAM_ID_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "stream_id", "event class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, stream_id);
+
+            /*
+             * Only read "stream_id" if get_unary_unsigned()
+             * succeeded.
+             */
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Unexpected unary expression for event class's `stream_id` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            _SET(set, _EVENT_STREAM_ID_SET);
+        } else if (strcmp(left, "context") == 0) {
+            if (_IS_SET(set, _EVENT_CONTEXT_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate `context` entry in event class.");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = visit_field_class_specifier_list(
+                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
+                &event_class->spec_context_fc);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                                "Cannot create event class's context field class.");
+                goto error;
+            }
+
+            BT_ASSERT(event_class->spec_context_fc);
+            _SET(set, _EVENT_CONTEXT_SET);
+        } else if (strcmp(left, "fields") == 0) {
+            if (_IS_SET(set, _EVENT_FIELDS_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate `fields` entry in event class.");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = visit_field_class_specifier_list(
+                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
+                &event_class->payload_fc);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                                "Cannot create event class's payload field class.");
+                goto error;
+            }
+
+            BT_ASSERT(event_class->payload_fc);
+            _SET(set, _EVENT_FIELDS_SET);
+        } else if (strcmp(left, "loglevel") == 0) {
+            uint64_t loglevel_value;
+            bool is_log_level_known = true;
+            bt_event_class_log_level log_level;
+
+            if (_IS_SET(set, _EVENT_LOG_LEVEL_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "loglevel", "event class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, &loglevel_value);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Unexpected unary expression for event class's `loglevel` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            switch (loglevel_value) {
+            case 0:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_EMERGENCY;
+                break;
+            case 1:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_ALERT;
+                break;
+            case 2:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_CRITICAL;
+                break;
+            case 3:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_ERROR;
+                break;
+            case 4:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_WARNING;
+                break;
+            case 5:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_NOTICE;
+                break;
+            case 6:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_INFO;
+                break;
+            case 7:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_SYSTEM;
+                break;
+            case 8:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_PROGRAM;
+                break;
+            case 9:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_PROCESS;
+                break;
+            case 10:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_MODULE;
+                break;
+            case 11:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_UNIT;
+                break;
+            case 12:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_FUNCTION;
+                break;
+            case 13:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_LINE;
+                break;
+            case 14:
+                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG;
+                break;
+            default:
+                is_log_level_known = false;
+                _BT_COMP_LOGW_NODE(
+                    node,
+                    "Not setting event class's log level because its value is unknown: "
+                    "log-level=%" PRIu64,
+                    loglevel_value);
+            }
+
+            if (is_log_level_known) {
+                ctf_event_class_set_log_level(event_class, log_level);
+            }
+
+            _SET(set, _EVENT_LOG_LEVEL_SET);
+        } else if (strcmp(left, "model.emf.uri") == 0) {
+            char *right;
+
+            if (_IS_SET(set, _EVENT_MODEL_EMF_URI_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "model.emf.uri", "event class");
+                ret = -EPERM;
+                goto error;
+            }
+
+            right = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.right);
+            if (!right) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node,
+                    "Unexpected unary expression for event class's `model.emf.uri` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (strlen(right) == 0) {
+                _BT_COMP_LOGW_NODE(node, "Not setting event class's EMF URI because it's empty.");
+            } else {
+                g_string_assign(event_class->emf_uri, right);
+            }
+
+            g_free(right);
+            _SET(set, _EVENT_MODEL_EMF_URI_SET);
+        } else {
+            _BT_COMP_LOGW_NODE(node,
+                               "Unknown attribute in event class: "
+                               "attr-name=\"%s\"",
+                               left);
+        }
+
+        g_free(left);
+        left = NULL;
+        break;
+    }
+    default:
+        ret = -EPERM;
+        goto error;
+    }
+
+    goto end;
+
+error:
+    g_free(left);
+
+end:
+    return ret;
+}
+
+static char *get_event_decl_name(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
+{
+    char *left = NULL;
+    char *name = NULL;
+    struct ctf_node *iter;
+    struct bt_list_head *decl_list = &node->u.event.declaration_list;
+
+    bt_list_for_each_entry (iter, decl_list, siblings) {
+        if (iter->type != NODE_CTF_EXPRESSION) {
+            continue;
+        }
+
+        left = ctf_ast_concatenate_unary_strings(&iter->u.ctf_expression.left);
+        if (!left) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot concatenate unary strings.");
+            goto error;
+        }
+
+        if (strcmp(left, "name") == 0) {
+            name = ctf_ast_concatenate_unary_strings(&iter->u.ctf_expression.right);
+            if (!name) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    iter, "Unexpected unary expression for event class's `name` attribute.");
+                goto error;
+            }
+        }
+
+        g_free(left);
+        left = NULL;
+
+        if (name) {
+            break;
+        }
+    }
+
+    return name;
+
+error:
+    g_free(left);
+    return NULL;
+}
+
+static int visit_event_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
+{
+    int ret = 0;
+    int set = 0;
+    struct ctf_node *iter;
+    uint64_t stream_id = 0;
+    char *event_name = NULL;
+    struct ctf_event_class *event_class = NULL;
+    struct ctf_stream_class *stream_class = NULL;
+    struct bt_list_head *decl_list = &node->u.event.declaration_list;
+    bool pop_scope = false;
+
+    if (node->visited) {
+        goto end;
+    }
+
+    node->visited = TRUE;
+    event_name = get_event_decl_name(ctx, node);
+    if (!event_name) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Missing `name` attribute in event class.");
+        ret = -EPERM;
+        goto error;
+    }
+
+    event_class = ctf_event_class_create();
+    BT_ASSERT(event_class);
+    g_string_assign(event_class->name, event_name);
+    _TRY_PUSH_SCOPE_OR_GOTO_ERROR();
+    pop_scope = true;
+
+    bt_list_for_each_entry (iter, decl_list, siblings) {
+        ret = visit_event_decl_entry(ctx, iter, event_class, &stream_id, &set);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
+                                            "Cannot visit event class's entry: "
+                                            "ret=%d",
+                                            ret);
+            goto error;
+        }
+    }
+
+    if (!_IS_SET(&set, _EVENT_STREAM_ID_SET)) {
+        /*
+         * Allow missing stream_id if there is only a single
+         * stream class.
+         */
+        switch (ctx->ctf_tc->stream_classes->len) {
+        case 0:
+            /* Create implicit stream class if there's none */
+            stream_id = 0;
+            stream_class = ctf_stream_class_create();
+            BT_ASSERT(stream_class);
+            stream_class->id = stream_id;
+            g_ptr_array_add(ctx->ctf_tc->stream_classes, stream_class);
+            break;
+        case 1:
+            /* Single stream class: get its ID */
+            stream_class = (ctf_stream_class *) ctx->ctf_tc->stream_classes->pdata[0];
+            stream_id = stream_class->id;
+            break;
+        default:
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Missing `stream_id` attribute in event class.");
+            ret = -EPERM;
+            goto error;
+        }
+    }
+
+    /* We have the stream ID now; get the stream class if found */
+    if (!stream_class) {
+        stream_class = ctf_trace_class_borrow_stream_class_by_id(ctx->ctf_tc, stream_id);
+        if (!stream_class) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                            "Cannot find stream class at this point: "
+                                            "id=%" PRId64,
+                                            stream_id);
+            ret = -EINVAL;
+            goto error;
+        }
+    }
+
+    BT_ASSERT(stream_class);
+
+    if (!_IS_SET(&set, _EVENT_ID_SET)) {
+        /* Allow only one event without ID per stream */
+        if (stream_class->event_classes->len != 0) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Missing `id` attribute in event class.");
+            ret = -EPERM;
+            goto error;
+        }
+
+        /* Automatic ID */
+        event_class->id = 0;
+    }
+
+    if (ctf_stream_class_borrow_event_class_by_id(stream_class, event_class->id)) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                        "Duplicate event class (same ID) in the same stream class: "
+                                        "id=%" PRId64,
+                                        event_class->id);
+        ret = -EEXIST;
+        goto error;
+    }
+
+    ctf_stream_class_append_event_class(stream_class, event_class);
+    event_class = NULL;
+    goto end;
+
+error:
+    ctf_event_class_destroy(event_class);
+    event_class = NULL;
+
+    if (ret >= 0) {
+        ret = -1;
+    }
+
+end:
+    if (pop_scope) {
+        ctx_pop_scope(ctx);
+    }
+
+    g_free(event_name);
+
+    return ret;
+}
+
+static int auto_map_field_to_trace_clock_class(struct ctf_visitor_generate_ir *ctx,
+                                               struct ctf_field_class *fc)
+{
+    struct ctf_clock_class *clock_class_to_map_to = NULL;
+    uint64_t clock_class_count;
+
+    if (!fc) {
+        return 0;
+    }
+
+    if (fc->type != CTF_FIELD_CLASS_TYPE_INT && fc->type != CTF_FIELD_CLASS_TYPE_ENUM) {
+        return 0;
+    }
+
+    ctf_field_class_int *int_fc = ctf_field_class_as_int(fc);
+
+    if (int_fc->mapped_clock_class) {
+        /* Already mapped */
+        return 0;
+    }
+
+    clock_class_count = ctx->ctf_tc->clock_classes->len;
+
+    switch (clock_class_count) {
+    case 0:
+        /*
+         * No clock class exists in the trace at this point. Create an
+         * implicit one at 1 GHz, named `default`, and use this clock
+         * class.
+         */
+        clock_class_to_map_to = ctf_clock_class_create();
+        BT_ASSERT(clock_class_to_map_to);
+        clock_class_to_map_to->frequency = UINT64_C(1000000000);
+        g_string_assign(clock_class_to_map_to->name, "default");
+        g_ptr_array_add(ctx->ctf_tc->clock_classes, clock_class_to_map_to);
+        break;
+    case 1:
+        /*
+         * Only one clock class exists in the trace at this point: use
+         * this one.
+         */
+        clock_class_to_map_to = (ctf_clock_class *) ctx->ctf_tc->clock_classes->pdata[0];
+        break;
+    default:
+        /*
+         * Timestamp field not mapped to a clock class and there's more
+         * than one clock class in the trace: this is an error.
+         */
+        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+            "Timestamp field found with no mapped clock class, "
+            "but there's more than one clock class in the trace at this point.");
+        return -1;
+    }
+
+    BT_ASSERT(clock_class_to_map_to);
+    int_fc->mapped_clock_class = clock_class_to_map_to;
+
+    return 0;
+}
+
+static int auto_map_fields_to_trace_clock_class(struct ctf_visitor_generate_ir *ctx,
+                                                struct ctf_field_class *root_fc,
+                                                const char *field_name)
+{
+    int ret = 0;
+    uint64_t i, count;
+    struct ctf_field_class_struct *struct_fc = (ctf_field_class_struct *) root_fc;
+    struct ctf_field_class_variant *var_fc = (ctf_field_class_variant *) root_fc;
+
+    if (!root_fc) {
+        goto end;
+    }
+
+    if (root_fc->type != CTF_FIELD_CLASS_TYPE_STRUCT &&
+        root_fc->type != CTF_FIELD_CLASS_TYPE_VARIANT) {
+        goto end;
+    }
+
+    if (root_fc->type == CTF_FIELD_CLASS_TYPE_STRUCT) {
+        count = struct_fc->members->len;
+    } else {
+        count = var_fc->options->len;
+    }
+
+    for (i = 0; i < count; i++) {
+        struct ctf_named_field_class *named_fc = NULL;
+
+        if (root_fc->type == CTF_FIELD_CLASS_TYPE_STRUCT) {
+            named_fc = ctf_field_class_struct_borrow_member_by_index(struct_fc, i);
+        } else if (root_fc->type == CTF_FIELD_CLASS_TYPE_VARIANT) {
+            named_fc = ctf_field_class_variant_borrow_option_by_index(var_fc, i);
+        } else {
+            bt_common_abort();
+        }
+
+        if (strcmp(named_fc->name->str, field_name) == 0) {
+            ret = auto_map_field_to_trace_clock_class(ctx, named_fc->fc);
+            if (ret) {
+                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                    "Cannot automatically map field to trace's clock class: "
+                    "field-name=\"%s\"",
+                    field_name);
+                goto end;
+            }
+        }
+
+        ret = auto_map_fields_to_trace_clock_class(ctx, named_fc->fc, field_name);
+        if (ret) {
+            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
+                "Cannot automatically map structure or variant field class's fields to trace's clock class: "
+                "field-name=\"%s\", root-field-name=\"%s\"",
+                field_name, named_fc->name->str);
+            goto end;
+        }
+    }
+
+end:
+    return ret;
+}
+
+static int visit_stream_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node,
+                                   struct ctf_stream_class *stream_class, int *set)
+{
+    int ret = 0;
+    char *left = NULL;
+
+    switch (node->type) {
+    case NODE_TYPEDEF:
+        ret = visit_field_class_def(ctx, node->u.field_class_def.field_class_specifier_list,
+                                    &node->u.field_class_def.field_class_declarators);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot add field class found in stream class.");
+            goto error;
+        }
+        break;
+    case NODE_TYPEALIAS:
+        ret = visit_field_class_alias(ctx, node->u.field_class_alias.target,
+                                      node->u.field_class_alias.alias);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                            "Cannot add field class alias found in stream class.");
+            goto error;
+        }
+        break;
+    case NODE_CTF_EXPRESSION:
+    {
+        left = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.left);
+        if (!left) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot concatenate unary strings.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (strcmp(left, "id") == 0) {
+            int64_t id;
+
+            if (_IS_SET(set, _STREAM_ID_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "id", "stream declaration");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, (uint64_t *) &id);
+
+            /* Only read "id" if get_unary_unsigned() succeeded. */
+            if (ret || (!ret && id < 0)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Unexpected unary expression for stream class's `id` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (ctf_trace_class_borrow_stream_class_by_id(ctx->ctf_tc, id)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Duplicate stream class (same ID): id=%" PRId64, id);
+                ret = -EEXIST;
+                goto error;
+            }
+
+            stream_class->id = id;
+            _SET(set, _STREAM_ID_SET);
+        } else if (strcmp(left, "event.header") == 0) {
+            if (_IS_SET(set, _STREAM_EVENT_HEADER_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                                "Duplicate `event.header` entry in stream class.");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = visit_field_class_specifier_list(
+                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
+                &stream_class->event_header_fc);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Cannot create stream class's event header field class.");
+                goto error;
+            }
+
+            BT_ASSERT(stream_class->event_header_fc);
+            ret = auto_map_fields_to_trace_clock_class(ctx, stream_class->event_header_fc,
+                                                       "timestamp");
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node,
+                    "Cannot automatically map specific event header field class fields named `timestamp` to trace's clock class.");
+                goto error;
+            }
+
+            _SET(set, _STREAM_EVENT_HEADER_SET);
+        } else if (strcmp(left, "event.context") == 0) {
+            if (_IS_SET(set, _STREAM_EVENT_CONTEXT_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                                "Duplicate `event.context` entry in stream class.");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = visit_field_class_specifier_list(
+                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
+                &stream_class->event_common_context_fc);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Cannot create stream class's event context field class.");
+                goto error;
+            }
+
+            BT_ASSERT(stream_class->event_common_context_fc);
+            _SET(set, _STREAM_EVENT_CONTEXT_SET);
+        } else if (strcmp(left, "packet.context") == 0) {
+            if (_IS_SET(set, _STREAM_PACKET_CONTEXT_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Duplicate `packet.context` entry in stream class.");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = visit_field_class_specifier_list(
+                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
+                &stream_class->packet_context_fc);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Cannot create stream class's packet context field class.");
+                goto error;
+            }
+
+            BT_ASSERT(stream_class->packet_context_fc);
+            ret = auto_map_fields_to_trace_clock_class(ctx, stream_class->packet_context_fc,
+                                                       "timestamp_begin");
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node,
+                    "Cannot automatically map specific packet context field class fields named `timestamp_begin` to trace's clock class.");
+                goto error;
+            }
+
+            ret = auto_map_fields_to_trace_clock_class(ctx, stream_class->packet_context_fc,
+                                                       "timestamp_end");
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node,
+                    "Cannot automatically map specific packet context field class fields named `timestamp_end` to trace's clock class.");
+                goto error;
+            }
+
+            _SET(set, _STREAM_PACKET_CONTEXT_SET);
+        } else {
+            _BT_COMP_LOGW_NODE(node,
+                               "Unknown attribute in stream class: "
+                               "attr-name=\"%s\"",
+                               left);
+        }
+
+        g_free(left);
+        left = NULL;
+        break;
+    }
+
+    default:
+        ret = -EPERM;
+        goto error;
+    }
+
+    return 0;
+
+error:
+    g_free(left);
+    return ret;
+}
+
+static int visit_stream_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
+{
+    int set = 0;
+    int ret = 0;
+    struct ctf_node *iter;
+    struct ctf_stream_class *stream_class = NULL;
+    struct bt_list_head *decl_list = &node->u.stream.declaration_list;
+
+    if (node->visited) {
+        goto end;
+    }
+
+    node->visited = TRUE;
+    stream_class = ctf_stream_class_create();
+    BT_ASSERT(stream_class);
+    _TRY_PUSH_SCOPE_OR_GOTO_ERROR();
+
+    bt_list_for_each_entry (iter, decl_list, siblings) {
+        ret = visit_stream_decl_entry(ctx, iter, stream_class, &set);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
+                                            "Cannot visit stream class's entry: "
+                                            "ret=%d",
+                                            ret);
+            ctx_pop_scope(ctx);
+            goto error;
+        }
+    }
+
+    ctx_pop_scope(ctx);
+
+    if (_IS_SET(&set, _STREAM_ID_SET)) {
+        /* Check that packet header has `stream_id` field */
+        struct ctf_named_field_class *named_fc = NULL;
+
+        if (!ctx->ctf_tc->packet_header_fc) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Stream class has a `id` attribute, "
+                                                  "but trace has no packet header field class.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        named_fc = ctf_field_class_struct_borrow_member_by_name(
+            ctf_field_class_as_struct(ctx->ctf_tc->packet_header_fc), "stream_id");
+        if (!named_fc) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                node, "Stream class has a `id` attribute, "
+                      "but trace's packet header field class has no `stream_id` field.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (named_fc->fc->type != CTF_FIELD_CLASS_TYPE_INT &&
+            named_fc->fc->type != CTF_FIELD_CLASS_TYPE_ENUM) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                node,
+                "Stream class has a `id` attribute, "
+                "but trace's packet header field class's `stream_id` field is not an integer field class.");
+            ret = -EINVAL;
+            goto error;
+        }
+    } else {
+        /* Allow only _one_ ID-less stream */
+        if (ctx->ctf_tc->stream_classes->len != 0) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                node,
+                "Missing `id` attribute in stream class as there's more than one stream class in the trace.");
+            ret = -EPERM;
+            goto error;
+        }
+
+        /* Automatic ID: 0 */
+        stream_class->id = 0;
+    }
+
+    /*
+     * Make sure that this stream class's ID is currently unique in
+     * the trace.
+     */
+    if (ctf_trace_class_borrow_stream_class_by_id(ctx->ctf_tc, stream_class->id)) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate stream class (same ID): id=%" PRId64,
+                                        stream_class->id);
+        ret = -EINVAL;
+        goto error;
+    }
+
+    g_ptr_array_add(ctx->ctf_tc->stream_classes, stream_class);
+    stream_class = NULL;
+    goto end;
+
+error:
+    ctf_stream_class_destroy(stream_class);
+    stream_class = NULL;
+
+end:
+    return ret;
+}
+
+static int visit_trace_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node,
+                                  int *set)
+{
+    int ret = 0;
+    char *left = NULL;
+    uint64_t val;
+
+    switch (node->type) {
+    case NODE_TYPEDEF:
+        ret = visit_field_class_def(ctx, node->u.field_class_def.field_class_specifier_list,
+                                    &node->u.field_class_def.field_class_declarators);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                node, "Cannot add field class found in trace (`trace` block).");
+            goto error;
+        }
+        break;
+    case NODE_TYPEALIAS:
+        ret = visit_field_class_alias(ctx, node->u.field_class_alias.target,
+                                      node->u.field_class_alias.alias);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                node, "Cannot add field class alias found in trace (`trace` block).");
+            goto error;
+        }
+        break;
+    case NODE_CTF_EXPRESSION:
+    {
+        left = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.left);
+        if (!left) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot concatenate unary strings.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (strcmp(left, "major") == 0) {
+            if (_IS_SET(set, _TRACE_MAJOR_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "major", "trace");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, &val);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Unexpected unary expression for trace's `major` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (val != 1) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                                "Invalid trace's `minor` attribute: expecting 1.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            ctx->ctf_tc->major = val;
+            _SET(set, _TRACE_MAJOR_SET);
+        } else if (strcmp(left, "minor") == 0) {
+            if (_IS_SET(set, _TRACE_MINOR_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "minor", "trace");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, &val);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    node, "Unexpected unary expression for trace's `minor` attribute.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (val != 8) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                                "Invalid trace's `minor` attribute: expecting 8.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            ctx->ctf_tc->minor = val;
+            _SET(set, _TRACE_MINOR_SET);
+        } else if (strcmp(left, "uuid") == 0) {
+            if (_IS_SET(set, _TRACE_UUID_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "uuid", "trace");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = get_unary_uuid(ctx, &node->u.ctf_expression.right, ctx->ctf_tc->uuid);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Invalid trace's `uuid` attribute.");
+                goto error;
+            }
+
+            ctx->ctf_tc->is_uuid_set = true;
+            _SET(set, _TRACE_UUID_SET);
+        } else if (strcmp(left, "byte_order") == 0) {
+            /* Default byte order is already known at this stage */
+            if (_IS_SET(set, _TRACE_BYTE_ORDER_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "byte_order", "trace");
+                ret = -EPERM;
+                goto error;
+            }
+
+            BT_ASSERT(ctx->ctf_tc->default_byte_order != CTF_BYTE_ORDER_UNKNOWN);
+            _SET(set, _TRACE_BYTE_ORDER_SET);
+        } else if (strcmp(left, "packet.header") == 0) {
+            if (_IS_SET(set, _TRACE_PACKET_HEADER_SET)) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate `packet.header` entry in trace.");
+                ret = -EPERM;
+                goto error;
+            }
+
+            ret = visit_field_class_specifier_list(
+                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
+                &ctx->ctf_tc->packet_header_fc);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                                "Cannot create trace's packet header field class.");
+                goto error;
+            }
+
+            BT_ASSERT(ctx->ctf_tc->packet_header_fc);
+            _SET(set, _TRACE_PACKET_HEADER_SET);
+        } else {
+            _BT_COMP_LOGW_NODE(node,
+                               "Unknown attribute in stream class: "
+                               "attr-name=\"%s\"",
+                               left);
+        }
+
+        g_free(left);
+        left = NULL;
+        break;
+    }
+    default:
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Unknown expression in trace.");
+        ret = -EINVAL;
+        goto error;
+    }
+
+    return 0;
+
+error:
+    g_free(left);
+    return ret;
+}
+
+static int visit_trace_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
+{
+    int ret = 0;
+    int set = 0;
+    struct ctf_node *iter;
+    struct bt_list_head *decl_list = &node->u.trace.declaration_list;
+
+    if (node->visited) {
+        goto end;
+    }
+
+    node->visited = TRUE;
+
+    if (ctx->is_trace_visited) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate trace (`trace` block).");
+        ret = -EEXIST;
+        goto error;
+    }
+
+    _TRY_PUSH_SCOPE_OR_GOTO_ERROR();
+
+    bt_list_for_each_entry (iter, decl_list, siblings) {
+        ret = visit_trace_decl_entry(ctx, iter, &set);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
+                                            "Cannot visit trace's entry (`trace` block): "
+                                            "ret=%d",
+                                            ret);
+            ctx_pop_scope(ctx);
+            goto error;
+        }
+    }
+
+    ctx_pop_scope(ctx);
+
+    if (!_IS_SET(&set, _TRACE_MAJOR_SET)) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                        "Missing `major` attribute in trace (`trace` block).");
+        ret = -EPERM;
+        goto error;
+    }
+
+    if (!_IS_SET(&set, _TRACE_MINOR_SET)) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                        "Missing `minor` attribute in trace (`trace` block).");
+        ret = -EPERM;
+        goto error;
+    }
+
+    if (!_IS_SET(&set, _TRACE_BYTE_ORDER_SET)) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                        "Missing `byte_order` attribute in trace (`trace` block).");
+        ret = -EPERM;
+        goto error;
+    }
+
+    ctx->is_trace_visited = true;
+
+end:
+    return 0;
+
+error:
+    return ret;
+}
+
+static int visit_env(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
+{
+    int ret = 0;
+    char *left = NULL;
+    struct ctf_node *entry_node;
+    struct bt_list_head *decl_list = &node->u.env.declaration_list;
+
+    if (node->visited) {
+        goto end;
+    }
+
+    node->visited = TRUE;
+
+    bt_list_for_each_entry (entry_node, decl_list, siblings) {
+        struct bt_list_head *right_head = &entry_node->u.ctf_expression.right;
+
+        if (entry_node->type != NODE_CTF_EXPRESSION) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node,
+                                            "Wrong expression in environment entry: "
+                                            "node-type=%d",
+                                            entry_node->type);
+            ret = -EPERM;
+            goto error;
+        }
+
+        left = ctf_ast_concatenate_unary_strings(&entry_node->u.ctf_expression.left);
+        if (!left) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Cannot get environment entry's name.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (is_unary_string(right_head)) {
+            char *right = ctf_ast_concatenate_unary_strings(right_head);
+
+            if (!right) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    entry_node,
+                    "Unexpected unary expression for environment entry's value: "
+                    "name=\"%s\"",
+                    left);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (strcmp(left, "tracer_name") == 0) {
+                if (strncmp(right, "lttng", 5) == 0) {
+                    BT_COMP_LOGI("Detected LTTng trace from `%s` environment value: "
+                                 "tracer-name=\"%s\"",
+                                 left, right);
+                    ctx->is_lttng = true;
+                }
+            }
+
+            ctf_trace_class_append_env_entry(ctx->ctf_tc, left, CTF_TRACE_CLASS_ENV_ENTRY_TYPE_STR,
+                                             right, 0);
+            g_free(right);
+        } else if (is_unary_unsigned(right_head) || is_unary_signed(right_head)) {
+            int64_t v;
+
+            if (is_unary_unsigned(right_head)) {
+                ret = get_unary_unsigned(ctx, right_head, (uint64_t *) &v);
+            } else {
+                ret = get_unary_signed(right_head, &v);
+            }
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    entry_node,
+                    "Unexpected unary expression for environment entry's value: "
+                    "name=\"%s\"",
+                    left);
+                ret = -EINVAL;
+                goto error;
+            }
+
+            ctf_trace_class_append_env_entry(ctx->ctf_tc, left, CTF_TRACE_CLASS_ENV_ENTRY_TYPE_INT,
+                                             NULL, v);
+        } else {
+            _BT_COMP_LOGW_NODE(entry_node,
+                               "Environment entry has unknown type: "
+                               "name=\"%s\"",
+                               left);
+        }
+
+        g_free(left);
+        left = NULL;
+    }
+
+end:
+    return 0;
+
+error:
+    g_free(left);
+    return ret;
+}
+
+static int set_trace_byte_order(struct ctf_visitor_generate_ir *ctx, struct ctf_node *trace_node)
+{
+    int ret = 0;
+    int set = 0;
+    char *left = NULL;
+    struct ctf_node *node;
+    struct bt_list_head *decl_list = &trace_node->u.trace.declaration_list;
+
+    bt_list_for_each_entry (node, decl_list, siblings) {
+        if (node->type == NODE_CTF_EXPRESSION) {
+            struct ctf_node *right_node;
+
+            left = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.left);
+            if (!left) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot concatenate unary strings.");
+                ret = -EINVAL;
+                goto error;
+            }
+
+            if (strcmp(left, "byte_order") == 0) {
+                enum ctf_byte_order bo;
+
+                if (_IS_SET(&set, _TRACE_BYTE_ORDER_SET)) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "byte_order", "trace");
+                    ret = -EPERM;
+                    goto error;
+                }
+
+                _SET(&set, _TRACE_BYTE_ORDER_SET);
+                right_node =
+                    _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings);
+                bo = byte_order_from_unary_expr(ctx, right_node);
+                if (bo == CTF_BYTE_ORDER_UNKNOWN) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                        node, "Invalid `byte_order` attribute in trace (`trace` block): "
+                              "expecting `le`, `be`, or `network`.");
+                    ret = -EINVAL;
+                    goto error;
+                } else if (bo == CTF_BYTE_ORDER_DEFAULT) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                        node, "Invalid `byte_order` attribute in trace (`trace` block): "
+                              "cannot be set to `native` here.");
+                    ret = -EPERM;
+                    goto error;
+                }
+
+                ctx->ctf_tc->default_byte_order = bo;
+            }
+
+            g_free(left);
+            left = NULL;
+        }
+    }
+
+    if (!_IS_SET(&set, _TRACE_BYTE_ORDER_SET)) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(trace_node,
+                                        "Missing `byte_order` attribute in trace (`trace` block).");
+        ret = -EINVAL;
+        goto error;
+    }
+
+    return 0;
+
+error:
+    g_free(left);
+    return ret;
+}
+
+static int visit_clock_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *entry_node,
+                                  struct ctf_clock_class *clock, int *set, int64_t *offset_seconds,
+                                  uint64_t *offset_cycles)
+{
+    int ret = 0;
+    char *left = NULL;
+
+    if (entry_node->type != NODE_CTF_EXPRESSION) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Unexpected node type: node-type=%d",
+                                        entry_node->type);
+        ret = -EPERM;
+        goto error;
+    }
+
+    left = ctf_ast_concatenate_unary_strings(&entry_node->u.ctf_expression.left);
+    if (!left) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Cannot concatenate unary strings.");
+        ret = -EINVAL;
+        goto error;
+    }
+
+    if (strcmp(left, "name") == 0) {
+        char *right;
+
+        if (_IS_SET(set, _CLOCK_NAME_SET)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "name", "clock class");
+            ret = -EPERM;
+            goto error;
+        }
+
+        right = ctf_ast_concatenate_unary_strings(&entry_node->u.ctf_expression.right);
+        if (!right) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Unexpected unary expression for clock class's `name` attribute.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        g_string_assign(clock->name, right);
+        g_free(right);
+        _SET(set, _CLOCK_NAME_SET);
+    } else if (strcmp(left, "uuid") == 0) {
+        bt_uuid_t uuid;
+
+        if (_IS_SET(set, _CLOCK_UUID_SET)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "uuid", "clock class");
+            ret = -EPERM;
+            goto error;
+        }
+
+        ret = get_unary_uuid(ctx, &entry_node->u.ctf_expression.right, uuid);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Invalid clock class's `uuid` attribute.");
+            goto error;
+        }
+
+        clock->has_uuid = true;
+        bt_uuid_copy(clock->uuid, uuid);
+        _SET(set, _CLOCK_UUID_SET);
+    } else if (strcmp(left, "description") == 0) {
+        char *right;
+
+        if (_IS_SET(set, _CLOCK_DESCRIPTION_SET)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "description", "clock class");
+            ret = -EPERM;
+            goto error;
+        }
+
+        right = ctf_ast_concatenate_unary_strings(&entry_node->u.ctf_expression.right);
+        if (!right) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node,
+                "Unexpected unary expression for clock class's `description` attribute.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        g_string_assign(clock->description, right);
+        g_free(right);
+        _SET(set, _CLOCK_DESCRIPTION_SET);
+    } else if (strcmp(left, "freq") == 0) {
+        uint64_t freq = UINT64_C(-1);
+
+        if (_IS_SET(set, _CLOCK_FREQ_SET)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "freq", "clock class");
+            ret = -EPERM;
+            goto error;
+        }
+
+        ret = get_unary_unsigned(ctx, &entry_node->u.ctf_expression.right, &freq);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Unexpected unary expression for clock class's `freq` attribute.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        if (freq == UINT64_C(-1) || freq == 0) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node,
+                                            "Invalid clock class frequency: freq=%" PRIu64, freq);
+            ret = -EINVAL;
+            goto error;
+        }
+
+        clock->frequency = freq;
+        _SET(set, _CLOCK_FREQ_SET);
+    } else if (strcmp(left, "precision") == 0) {
+        uint64_t precision;
+
+        if (_IS_SET(set, _CLOCK_PRECISION_SET)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "precision", "clock class");
+            ret = -EPERM;
+            goto error;
+        }
+
+        ret = get_unary_unsigned(ctx, &entry_node->u.ctf_expression.right, &precision);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Unexpected unary expression for clock class's `precision` attribute.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        clock->precision = precision;
+        _SET(set, _CLOCK_PRECISION_SET);
+    } else if (strcmp(left, "offset_s") == 0) {
+        if (_IS_SET(set, _CLOCK_OFFSET_S_SET)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "offset_s", "clock class");
+            ret = -EPERM;
+            goto error;
+        }
+
+        ret = get_unary_signed(&entry_node->u.ctf_expression.right, offset_seconds);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Unexpected unary expression for clock class's `offset_s` attribute.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        _SET(set, _CLOCK_OFFSET_S_SET);
+    } else if (strcmp(left, "offset") == 0) {
+        if (_IS_SET(set, _CLOCK_OFFSET_SET)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "offset", "clock class");
+            ret = -EPERM;
+            goto error;
+        }
+
+        ret = get_unary_unsigned(ctx, &entry_node->u.ctf_expression.right, offset_cycles);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Unexpected unary expression for clock class's `offset` attribute.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        _SET(set, _CLOCK_OFFSET_SET);
+    } else if (strcmp(left, "absolute") == 0) {
+        struct ctf_node *right;
+
+        if (_IS_SET(set, _CLOCK_ABSOLUTE_SET)) {
+            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "absolute", "clock class");
+            ret = -EPERM;
+            goto error;
+        }
+
+        right =
+            _BT_LIST_FIRST_ENTRY(&entry_node->u.ctf_expression.right, struct ctf_node, siblings);
+        ret = get_boolean(ctx, right);
+        if (ret < 0) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                entry_node, "Unexpected unary expression for clock class's `absolute` attribute.");
+            ret = -EINVAL;
+            goto error;
+        }
+
+        clock->is_absolute = ret;
+        _SET(set, _CLOCK_ABSOLUTE_SET);
+    } else {
+        _BT_COMP_LOGW_NODE(entry_node, "Unknown attribute in clock class: attr-name=\"%s\"", left);
+    }
+
+    g_free(left);
+    left = NULL;
+    return 0;
+
+error:
+    g_free(left);
+    return ret;
+}
+
+static inline uint64_t cycles_from_ns(uint64_t frequency, uint64_t ns)
+{
+    uint64_t cycles;
+
+    /* 1GHz */
+    if (frequency == UINT64_C(1000000000)) {
+        cycles = ns;
+    } else {
+        cycles = (uint64_t) (((double) ns * (double) frequency) / 1e9);
+    }
+
+    return cycles;
+}
+
+static void calibrate_clock_class_offsets(int64_t *offset_seconds, uint64_t *offset_cycles,
+                                          uint64_t freq)
+{
+    if (*offset_cycles >= freq) {
+        const uint64_t s_in_offset_cycles = *offset_cycles / freq;
+
+        *offset_seconds += (int64_t) s_in_offset_cycles;
+        *offset_cycles -= (s_in_offset_cycles * freq);
+    }
+}
+
+static void apply_clock_class_is_absolute(struct ctf_visitor_generate_ir *ctx,
+                                          struct ctf_clock_class *clock)
+{
+    if (ctx->decoder_config.force_clock_class_origin_unix_epoch) {
+        clock->is_absolute = true;
+    }
+
+    return;
+}
+
+static void apply_clock_class_offset(struct ctf_visitor_generate_ir *ctx,
+                                     struct ctf_clock_class *clock)
+{
+    uint64_t freq;
+    int64_t offset_s_to_apply = ctx->decoder_config.clock_class_offset_s;
+    uint64_t offset_ns_to_apply;
+    int64_t cur_offset_s;
+    uint64_t cur_offset_cycles;
+
+    if (ctx->decoder_config.clock_class_offset_s == 0 &&
+        ctx->decoder_config.clock_class_offset_ns == 0) {
+        goto end;
+    }
+
+    /* Transfer nanoseconds to seconds as much as possible */
+    if (ctx->decoder_config.clock_class_offset_ns < 0) {
+        const int64_t abs_ns = -ctx->decoder_config.clock_class_offset_ns;
+        const int64_t abs_extra_s = abs_ns / INT64_C(1000000000) + 1;
+        const int64_t extra_s = -abs_extra_s;
+        const int64_t offset_ns =
+            ctx->decoder_config.clock_class_offset_ns - (extra_s * INT64_C(1000000000));
+
+        BT_ASSERT(offset_ns > 0);
+        offset_ns_to_apply = (uint64_t) offset_ns;
+        offset_s_to_apply += extra_s;
+    } else {
+        const int64_t extra_s = ctx->decoder_config.clock_class_offset_ns / INT64_C(1000000000);
+        const int64_t offset_ns =
+            ctx->decoder_config.clock_class_offset_ns - (extra_s * INT64_C(1000000000));
+
+        BT_ASSERT(offset_ns >= 0);
+        offset_ns_to_apply = (uint64_t) offset_ns;
+        offset_s_to_apply += extra_s;
+    }
+
+    freq = clock->frequency;
+    cur_offset_s = clock->offset_seconds;
+    cur_offset_cycles = clock->offset_cycles;
+
+    /* Apply offsets */
+    cur_offset_s += offset_s_to_apply;
+    cur_offset_cycles += cycles_from_ns(freq, offset_ns_to_apply);
+
+    /*
+     * Recalibrate offsets because the part in cycles can be greater
+     * than the frequency at this point.
+     */
+    calibrate_clock_class_offsets(&cur_offset_s, &cur_offset_cycles, freq);
+
+    /* Set final offsets */
+    clock->offset_seconds = cur_offset_s;
+    clock->offset_cycles = cur_offset_cycles;
+
+end:
+    return;
+}
+
+static int visit_clock_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *clock_node)
+{
+    int ret = 0;
+    int set = 0;
+    struct ctf_clock_class *clock;
+    struct ctf_node *entry_node;
+    struct bt_list_head *decl_list = &clock_node->u.clock.declaration_list;
+    const char *clock_class_name;
+    int64_t offset_seconds = 0;
+    uint64_t offset_cycles = 0;
+    uint64_t freq;
+
+    if (clock_node->visited) {
+        return 0;
+    }
+
+    clock_node->visited = TRUE;
+
+    /* CTF 1.8's default frequency for a clock class is 1 GHz */
+    clock = ctf_clock_class_create();
+    if (!clock) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(clock_node, "Cannot create default clock class.");
+        ret = -ENOMEM;
+        goto end;
+    }
+
+    bt_list_for_each_entry (entry_node, decl_list, siblings) {
+        ret = visit_clock_decl_entry(ctx, entry_node, clock, &set, &offset_seconds, &offset_cycles);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Cannot visit clock class's entry: ret=%d",
+                                            ret);
+            goto end;
+        }
+    }
+
+    if (!_IS_SET(&set, _CLOCK_NAME_SET)) {
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(clock_node, "Missing `name` attribute in clock class.");
+        ret = -EPERM;
+        goto end;
+    }
+
+    clock_class_name = clock->name->str;
+    BT_ASSERT(clock_class_name);
+    if (ctx->is_lttng && strcmp(clock_class_name, "monotonic") == 0) {
+        /*
+         * Old versions of LTTng forgot to set its clock class
+         * as absolute, even if it is. This is important because
+         * it's a condition to be able to sort messages
+         * from different sources.
+         */
+        clock->is_absolute = true;
+    }
+
+    /*
+     * Adjust offsets so that the part in cycles is less than the
+     * frequency (move to the part in seconds).
+     */
+    freq = clock->frequency;
+    calibrate_clock_class_offsets(&offset_seconds, &offset_cycles, freq);
+    BT_ASSERT(offset_cycles < clock->frequency);
+    clock->offset_seconds = offset_seconds;
+    clock->offset_cycles = offset_cycles;
+    apply_clock_class_offset(ctx, clock);
+    apply_clock_class_is_absolute(ctx, clock);
+    g_ptr_array_add(ctx->ctf_tc->clock_classes, clock);
+    clock = NULL;
+
+end:
+    if (clock) {
+        ctf_clock_class_destroy(clock);
+    }
+
+    return ret;
+}
+
+static int visit_root_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *root_decl_node)
+{
+    int ret = 0;
+
+    if (root_decl_node->visited) {
+        goto end;
+    }
+
+    root_decl_node->visited = TRUE;
+
+    switch (root_decl_node->type) {
+    case NODE_TYPEDEF:
+        ret =
+            visit_field_class_def(ctx, root_decl_node->u.field_class_def.field_class_specifier_list,
+                                  &root_decl_node->u.field_class_def.field_class_declarators);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(root_decl_node,
+                                            "Cannot add field class found in root scope.");
+            goto end;
+        }
+        break;
+    case NODE_TYPEALIAS:
+        ret = visit_field_class_alias(ctx, root_decl_node->u.field_class_alias.target,
+                                      root_decl_node->u.field_class_alias.alias);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(root_decl_node,
+                                            "Cannot add field class alias found in root scope.");
+            goto end;
+        }
+        break;
+    case NODE_TYPE_SPECIFIER_LIST:
+    {
+        struct ctf_field_class *decl = NULL;
+
+        /*
+         * Just add the field class specifier to the root
+         * declaration scope. Put local reference.
+         */
+        ret = visit_field_class_specifier_list(ctx, root_decl_node, &decl);
+        if (ret) {
+            _BT_COMP_LOGE_APPEND_CAUSE_NODE(root_decl_node,
+                                            "Cannot visit root scope's field class: "
+                                            "ret=%d",
+                                            ret);
+            BT_ASSERT(!decl);
+            goto end;
+        }
+
+        ctf_field_class_destroy(decl);
+        decl = NULL;
+        break;
+    }
+    default:
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(root_decl_node, "Unexpected node type: node-type=%d",
+                                        root_decl_node->type);
+        ret = -EPERM;
+        goto end;
+    }
+
+end:
+    return ret;
+}
+
+struct ctf_visitor_generate_ir *
+ctf_visitor_generate_ir_create(const struct ctf_metadata_decoder_config *decoder_config)
+{
+    struct ctf_visitor_generate_ir *ctx = NULL;
+
+    /* Create visitor's context */
+    ctx = ctx_create(decoder_config);
+    if (!ctx) {
+        BT_COMP_LOG_CUR_LVL(BT_LOG_ERROR, decoder_config->log_level, decoder_config->self_comp,
+                            "Cannot create visitor's context.");
+        goto error;
+    }
+
+    goto end;
+
+error:
+    ctx_destroy(ctx);
+    ctx = NULL;
+
+end:
+    return ctx;
+}
+
+void ctf_visitor_generate_ir_destroy(struct ctf_visitor_generate_ir *visitor)
+{
+    ctx_destroy(visitor);
+}
+
+bt_trace_class *ctf_visitor_generate_ir_get_ir_trace_class(struct ctf_visitor_generate_ir *ctx)
+{
+    BT_ASSERT_DBG(ctx);
+
+    if (ctx->trace_class) {
+        bt_trace_class_get_ref(ctx->trace_class);
+    }
+
+    return ctx->trace_class;
+}
+
+struct ctf_trace_class *
+ctf_visitor_generate_ir_borrow_ctf_trace_class(struct ctf_visitor_generate_ir *ctx)
+{
+    BT_ASSERT_DBG(ctx);
+    BT_ASSERT_DBG(ctx->ctf_tc);
+    return ctx->ctf_tc;
+}
+
+int ctf_visitor_generate_ir_visit_node(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
+{
+    int ret = 0;
+
+    BT_COMP_LOGI_STR("Visiting metadata's AST to generate CTF IR objects.");
+
+    switch (node->type) {
+    case NODE_ROOT:
+    {
+        struct ctf_node *iter;
+        bool got_trace_decl = false;
+
+        /*
+         * The first thing we need is the native byte order of
+         * the trace block, because early class aliases can have
+         * a `byte_order` attribute set to `native`. If we don't
+         * have the native byte order yet, and we don't have any
+         * trace block yet, then fail with EINCOMPLETE.
+         */
+        if (ctx->ctf_tc->default_byte_order == CTF_BYTE_ORDER_UNKNOWN) {
+            bt_list_for_each_entry (iter, &node->u.root.trace, siblings) {
+                if (got_trace_decl) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate trace (`trace` block).");
+                    ret = -1;
+                    goto end;
+                }
+
+                ret = set_trace_byte_order(ctx, iter);
+                if (ret) {
+                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
+                                                    "Cannot set trace's native byte order: "
+                                                    "ret=%d",
+                                                    ret);
+                    goto end;
+                }
+
+                got_trace_decl = true;
+            }
+
+            if (!got_trace_decl) {
+                BT_COMP_LOGD_STR("Incomplete AST: need trace (`trace` block).");
+                ret = -EINCOMPLETE;
+                goto end;
+            }
+        }
+
+        BT_ASSERT(ctx->ctf_tc->default_byte_order == CTF_BYTE_ORDER_LITTLE ||
+                  ctx->ctf_tc->default_byte_order == CTF_BYTE_ORDER_BIG);
+        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
+
+        /* Environment */
+        bt_list_for_each_entry (iter, &node->u.root.env, siblings) {
+            ret = visit_env(ctx, iter);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
+                    iter,
+                    "Cannot visit trace's environment (`env` block) entry: "
+                    "ret=%d",
+                    ret);
+                goto end;
+            }
+        }
+
+        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
+
+        /*
+         * Visit clock blocks.
+         */
+        bt_list_for_each_entry (iter, &node->u.root.clock, siblings) {
+            ret = visit_clock_decl(ctx, iter);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit clock class: ret=%d", ret);
+                goto end;
+            }
+        }
+
+        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
+
+        /*
+         * Visit root declarations next, as they can be used by any
+         * following entity.
+         */
+        bt_list_for_each_entry (iter, &node->u.root.declaration_list, siblings) {
+            ret = visit_root_decl(ctx, iter);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit root entry: ret=%d", ret);
+                goto end;
+            }
+        }
+
+        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
+
+        /* Callsite blocks are not supported */
+        bt_list_for_each_entry (iter, &node->u.root.callsite, siblings) {
+            _BT_COMP_LOGW_NODE(iter, "\"callsite\" blocks are not supported as of this version.");
+        }
+
+        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
+
+        /* Trace */
+        bt_list_for_each_entry (iter, &node->u.root.trace, siblings) {
+            ret = visit_trace_decl(ctx, iter);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
+                                                "Cannot visit trace (`trace` block): "
+                                                "ret=%d",
+                                                ret);
+                goto end;
+            }
+        }
+
+        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
+
+        /* Streams */
+        bt_list_for_each_entry (iter, &node->u.root.stream, siblings) {
+            ret = visit_stream_decl(ctx, iter);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit stream class: ret=%d", ret);
+                goto end;
+            }
+        }
+
+        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
+
+        /* Events */
+        bt_list_for_each_entry (iter, &node->u.root.event, siblings) {
+            ret = visit_event_decl(ctx, iter);
+            if (ret) {
+                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit event class: ret=%d", ret);
+                goto end;
+            }
+        }
+
+        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
+        break;
+    }
+    default:
+        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Unexpected node type: node-type=%d", node->type);
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /* Update default clock classes */
+    ret = ctf_trace_class_update_default_clock_classes(ctx->ctf_tc, &ctx->log_cfg);
+    if (ret) {
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /* Update trace class meanings */
+    ret = ctf_trace_class_update_meanings(ctx->ctf_tc);
+    if (ret) {
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /* Update stream class configuration */
+    ret = ctf_trace_class_update_stream_class_config(ctx->ctf_tc);
+    if (ret) {
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /* Update text arrays and sequences */
+    ret = ctf_trace_class_update_text_array_sequence(ctx->ctf_tc);
+    if (ret) {
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /* Update structure/array/sequence alignments */
+    ret = ctf_trace_class_update_alignments(ctx->ctf_tc);
+    if (ret) {
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /* Resolve sequence lengths and variant tags */
+    ret = ctf_trace_class_resolve_field_classes(ctx->ctf_tc, &ctx->log_cfg);
+    if (ret) {
+        ret = -EINVAL;
+        goto end;
+    }
+
+    if (ctx->trace_class) {
+        /*
+         * Update "in IR" for field classes.
+         *
+         * If we have no IR trace class, then we'll have no way
+         * to create IR fields anyway, so we leave all the
+         * `in_ir` members false.
+         */
+        ret = ctf_trace_class_update_in_ir(ctx->ctf_tc);
+        if (ret) {
+            ret = -EINVAL;
+            goto end;
+        }
+    }
+
+    /* Update saved value indexes */
+    ret = ctf_trace_class_update_value_storing_indexes(ctx->ctf_tc);
+    if (ret) {
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /* Validate what we have so far */
+    ret = ctf_trace_class_validate(ctx->ctf_tc, &ctx->log_cfg);
+    if (ret) {
+        ret = -EINVAL;
+        goto end;
+    }
+
+    /*
+     * If there are fields which are not related to the CTF format
+     * itself in the packet header and in event header field
+     * classes, warn about it because they are never translated.
+     */
+    ctf_trace_class_warn_meaningless_header_fields(ctx->ctf_tc, &ctx->log_cfg);
+
+    if (ctx->trace_class) {
+        /* Copy new CTF metadata -> new IR metadata */
+        ret = ctf_trace_class_translate(ctx->log_cfg.self_comp, ctx->trace_class, ctx->ctf_tc);
+        if (ret) {
+            ret = -EINVAL;
+            goto end;
+        }
+    }
+
+end:
+    return ret;
+}
This page took 0.077616 seconds and 4 git commands to generate.