pco: define data structures and basic builder implementation with ops
Signed-off-by: Simon Perretta <simon.perretta@imgtec.com> Acked-by: Frank Binns <frank.binns@imgtec.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/32258>
This commit is contained in:
committed by
Marge Bot
parent
d47ac545d5
commit
f179e5a220
@@ -14,6 +14,15 @@ libpowervr_compiler_files = files(
|
||||
|
||||
pco_pygen_dep_files = files('pco_pygen_common.py', 'pco_isa.py', 'pco_ops.py')
|
||||
|
||||
pco_builder_ops_h = custom_target(
|
||||
'pco_builder_ops.h',
|
||||
input : ['pco_builder_ops.h.py'],
|
||||
output : 'pco_builder_ops.h',
|
||||
command : [prog_python, '@INPUT@'],
|
||||
capture : true,
|
||||
depend_files : pco_pygen_dep_files,
|
||||
)
|
||||
|
||||
pco_common_h = custom_target(
|
||||
'pco_common.h',
|
||||
input : ['pco_common.h.py'],
|
||||
@@ -52,6 +61,7 @@ pco_ops_h = custom_target(
|
||||
|
||||
idep_pco_pygen = declare_dependency(
|
||||
sources : [
|
||||
pco_builder_ops_h,
|
||||
pco_common_h,
|
||||
pco_info_c,
|
||||
pco_isa_h,
|
||||
|
||||
@@ -64,3 +64,39 @@ const nir_shader_compiler_options *pco_nir_options(pco_ctx *ctx)
|
||||
{
|
||||
return &ctx->nir_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Allocates and sets up a PCO instruction.
|
||||
*
|
||||
* \param[in,out] func Parent function.
|
||||
* \param[in] op Instruction op.
|
||||
* \param[in] num_dests Number of destinations.
|
||||
* \param[in] num_srcs Number of sources.
|
||||
* \return The PCO instruction, or NULL on failure.
|
||||
*/
|
||||
pco_instr *pco_instr_create(pco_func *func,
|
||||
enum pco_op op,
|
||||
unsigned num_dests,
|
||||
unsigned num_srcs)
|
||||
{
|
||||
pco_instr *instr;
|
||||
unsigned size = sizeof(*instr);
|
||||
size += num_dests * sizeof(*instr->dest);
|
||||
size += num_srcs * sizeof(*instr->src);
|
||||
|
||||
instr = rzalloc_size(func, size);
|
||||
|
||||
instr->parent_func = func;
|
||||
|
||||
instr->op = op;
|
||||
|
||||
instr->num_dests = num_dests;
|
||||
instr->dest = (pco_ref *)(instr + 1);
|
||||
|
||||
instr->num_srcs = num_srcs;
|
||||
instr->src = instr->dest + num_dests;
|
||||
|
||||
instr->index = func->next_instr++;
|
||||
|
||||
return instr;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "pco.h"
|
||||
#include "pco_internal.h"
|
||||
#include "pco_isa.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* Copyright © 2024 Imagination Technologies Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef PCO_BUILDER_H
|
||||
#define PCO_BUILDER_H
|
||||
|
||||
/**
|
||||
* \\file pco_builder.h
|
||||
*
|
||||
* \\brief PCO builder header.
|
||||
*/
|
||||
|
||||
#include "pco.h"
|
||||
#include "pco_internal.h"
|
||||
#include "util/list.h"
|
||||
#include "util/macros.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/** Cursor pointer. */
|
||||
enum pco_cursor_option {
|
||||
PCO_CURSOR_BEFORE_CF_NODE,
|
||||
PCO_CURSOR_AFTER_CF_NODE,
|
||||
|
||||
PCO_CURSOR_BEFORE_INSTR,
|
||||
PCO_CURSOR_AFTER_INSTR,
|
||||
|
||||
PCO_CURSOR_BEFORE_IGRP,
|
||||
PCO_CURSOR_AFTER_IGRP,
|
||||
};
|
||||
|
||||
/** Cursor for PCO instructions/groups and basic blocks. */
|
||||
typedef struct pco_cursor {
|
||||
enum pco_cursor_option option; /** Cursor pointer option. */
|
||||
union {
|
||||
pco_cf_node *cf_node;
|
||||
pco_instr *instr;
|
||||
pco_igrp *igrp;
|
||||
};
|
||||
} pco_cursor;
|
||||
|
||||
/** PCO builder context. */
|
||||
typedef struct pco_builder {
|
||||
pco_func *func; /** Target function. */
|
||||
pco_cursor cursor; /** Current position in the function. */
|
||||
} pco_builder;
|
||||
|
||||
/* Cursor position setters. */
|
||||
/**
|
||||
* \brief Returns a cursor set to before a cf node.
|
||||
*
|
||||
* \param[in] cf_node The cf node.
|
||||
* \return The cursor.
|
||||
*/
|
||||
static inline pco_cursor pco_cursor_before_cf_node(pco_cf_node *cf_node)
|
||||
{
|
||||
return (pco_cursor){
|
||||
.option = PCO_CURSOR_BEFORE_CF_NODE,
|
||||
.cf_node = cf_node,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a cursor set to after a cf node.
|
||||
*
|
||||
* \param[in] cf_node The cf node.
|
||||
* \return The cursor.
|
||||
*/
|
||||
static inline pco_cursor pco_cursor_after_cf_node(pco_cf_node *cf_node)
|
||||
{
|
||||
return (pco_cursor){
|
||||
.option = PCO_CURSOR_AFTER_CF_NODE,
|
||||
.cf_node = cf_node,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a cursor set to before a block.
|
||||
*
|
||||
* \param[in] block The block.
|
||||
* \return The cursor.
|
||||
*/
|
||||
static inline pco_cursor pco_cursor_before_block(pco_block *block)
|
||||
{
|
||||
return pco_cursor_before_cf_node(&block->cf_node);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a cursor set to after a block.
|
||||
*
|
||||
* \param[in] block The block.
|
||||
* \return The cursor.
|
||||
*/
|
||||
static inline pco_cursor pco_cursor_after_block(pco_block *block)
|
||||
{
|
||||
return pco_cursor_after_cf_node(&block->cf_node);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a cursor set to before an instruction.
|
||||
*
|
||||
* \param[in] instr The instruction.
|
||||
* \return The cursor.
|
||||
*/
|
||||
static inline pco_cursor pco_cursor_before_instr(pco_instr *instr)
|
||||
{
|
||||
return (pco_cursor){
|
||||
.option = PCO_CURSOR_BEFORE_INSTR,
|
||||
.instr = instr,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a cursor set to after an instruction.
|
||||
*
|
||||
* \param[in] instr The instruction.
|
||||
* \return The cursor.
|
||||
*/
|
||||
static inline pco_cursor pco_cursor_after_instr(pco_instr *instr)
|
||||
{
|
||||
return (pco_cursor){
|
||||
.option = PCO_CURSOR_AFTER_INSTR,
|
||||
.instr = instr,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a cursor set to before an instruction group.
|
||||
*
|
||||
* \param[in] igrp The instruction group.
|
||||
* \return The cursor.
|
||||
*/
|
||||
static inline pco_cursor pco_cursor_before_igrp(pco_igrp *igrp)
|
||||
{
|
||||
return (pco_cursor){
|
||||
.option = PCO_CURSOR_BEFORE_IGRP,
|
||||
.igrp = igrp,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns a cursor set to after an instruction group.
|
||||
*
|
||||
* \param[in] igrp The instruction group.
|
||||
* \return The cursor.
|
||||
*/
|
||||
static inline pco_cursor pco_cursor_after_igrp(pco_igrp *igrp)
|
||||
{
|
||||
return (pco_cursor){
|
||||
.option = PCO_CURSOR_AFTER_IGRP,
|
||||
.igrp = igrp,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns whether a cursor is set to before a construct.
|
||||
*
|
||||
* \param[in] cursor The cursor.
|
||||
* \return True if the cursor is set to before a construct.
|
||||
*/
|
||||
static inline bool pco_cursor_is_before(pco_cursor cursor)
|
||||
{
|
||||
return cursor.option == PCO_CURSOR_BEFORE_CF_NODE ||
|
||||
cursor.option == PCO_CURSOR_BEFORE_INSTR ||
|
||||
cursor.option == PCO_CURSOR_BEFORE_IGRP;
|
||||
}
|
||||
|
||||
/* Cursor get functions. */
|
||||
/**
|
||||
* \brief Returns the function being pointed to by the cursor.
|
||||
*
|
||||
* \param[in] cursor The cursor.
|
||||
* \return The function being pointed to.
|
||||
*/
|
||||
static inline pco_func *pco_cursor_func(pco_cursor cursor)
|
||||
{
|
||||
switch (cursor.option) {
|
||||
case PCO_CURSOR_BEFORE_CF_NODE:
|
||||
case PCO_CURSOR_AFTER_CF_NODE:
|
||||
switch (cursor.cf_node->type) {
|
||||
case PCO_CF_NODE_TYPE_BLOCK:
|
||||
return pco_cf_node_as_block(cursor.cf_node)->parent_func;
|
||||
|
||||
case PCO_CF_NODE_TYPE_IF:
|
||||
return pco_cf_node_as_if(cursor.cf_node)->parent_func;
|
||||
|
||||
case PCO_CF_NODE_TYPE_LOOP:
|
||||
return pco_cf_node_as_loop(cursor.cf_node)->parent_func;
|
||||
|
||||
case PCO_CF_NODE_TYPE_FUNC:
|
||||
return pco_cf_node_as_func(cursor.cf_node);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case PCO_CURSOR_BEFORE_INSTR:
|
||||
case PCO_CURSOR_AFTER_INSTR:
|
||||
return cursor.instr->parent_func;
|
||||
|
||||
case PCO_CURSOR_BEFORE_IGRP:
|
||||
case PCO_CURSOR_AFTER_IGRP:
|
||||
return cursor.igrp->parent_func;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the cf node being pointed to by the cursor.
|
||||
*
|
||||
* \param[in] cursor The cursor.
|
||||
* \return The cf node being pointed to.
|
||||
*/
|
||||
static inline pco_cf_node *pco_cursor_cf_node(pco_cursor cursor)
|
||||
{
|
||||
switch (cursor.option) {
|
||||
case PCO_CURSOR_BEFORE_CF_NODE:
|
||||
case PCO_CURSOR_AFTER_CF_NODE:
|
||||
return cursor.cf_node;
|
||||
|
||||
case PCO_CURSOR_BEFORE_INSTR:
|
||||
case PCO_CURSOR_AFTER_INSTR:
|
||||
return &cursor.instr->parent_block->cf_node;
|
||||
|
||||
case PCO_CURSOR_BEFORE_IGRP:
|
||||
case PCO_CURSOR_AFTER_IGRP:
|
||||
return &cursor.igrp->parent_block->cf_node;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the block being pointed to by the cursor.
|
||||
*
|
||||
* \param[in] cursor The cursor.
|
||||
* \return The block being pointed to.
|
||||
*/
|
||||
static inline pco_block *pco_cursor_block(pco_cursor cursor)
|
||||
{
|
||||
switch (cursor.option) {
|
||||
case PCO_CURSOR_BEFORE_CF_NODE:
|
||||
case PCO_CURSOR_AFTER_CF_NODE:
|
||||
switch (cursor.cf_node->type) {
|
||||
case PCO_CF_NODE_TYPE_BLOCK:
|
||||
return pco_cf_node_as_block(cursor.cf_node);
|
||||
|
||||
/* TODO: other cf node types? */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case PCO_CURSOR_BEFORE_INSTR:
|
||||
case PCO_CURSOR_AFTER_INSTR:
|
||||
return cursor.instr->parent_block;
|
||||
|
||||
case PCO_CURSOR_BEFORE_IGRP:
|
||||
case PCO_CURSOR_AFTER_IGRP:
|
||||
return cursor.igrp->parent_block;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the instruction being pointed to by the cursor.
|
||||
*
|
||||
* \param[in] cursor The cursor.
|
||||
* \return The instruction being pointed to.
|
||||
*/
|
||||
static inline pco_instr *pco_cursor_instr(pco_cursor cursor)
|
||||
{
|
||||
bool before = pco_cursor_is_before(cursor);
|
||||
|
||||
switch (cursor.option) {
|
||||
case PCO_CURSOR_BEFORE_CF_NODE:
|
||||
case PCO_CURSOR_AFTER_CF_NODE: {
|
||||
pco_block *block = NULL;
|
||||
|
||||
switch (cursor.cf_node->type) {
|
||||
case PCO_CF_NODE_TYPE_BLOCK:
|
||||
block = pco_cf_node_as_block(cursor.cf_node);
|
||||
return before ? pco_first_instr(block) : pco_last_instr(block);
|
||||
|
||||
/* TODO: other cf node types? */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PCO_CURSOR_BEFORE_INSTR:
|
||||
case PCO_CURSOR_AFTER_INSTR:
|
||||
return cursor.instr;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the instruction group being pointed to by the cursor.
|
||||
*
|
||||
* \param[in] cursor The cursor.
|
||||
* \return The instruction group being pointed to.
|
||||
*/
|
||||
static inline pco_igrp *pco_cursor_igrp(pco_cursor cursor)
|
||||
{
|
||||
bool before = pco_cursor_is_before(cursor);
|
||||
|
||||
switch (cursor.option) {
|
||||
case PCO_CURSOR_BEFORE_CF_NODE:
|
||||
case PCO_CURSOR_AFTER_CF_NODE: {
|
||||
pco_block *block = NULL;
|
||||
|
||||
switch (cursor.cf_node->type) {
|
||||
case PCO_CF_NODE_TYPE_BLOCK:
|
||||
block = pco_cf_node_as_block(cursor.cf_node);
|
||||
/* Special case: we're in pco_group_instrs and want to go from
|
||||
* the start.
|
||||
*/
|
||||
if (!block->parent_func->parent_shader->is_grouped)
|
||||
return NULL;
|
||||
return before ? pco_first_igrp(block) : pco_last_igrp(block);
|
||||
|
||||
/* TODO: other cf node types? */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PCO_CURSOR_BEFORE_IGRP:
|
||||
case PCO_CURSOR_AFTER_IGRP:
|
||||
return cursor.igrp;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
||||
/* Builder functions. */
|
||||
/**
|
||||
* \brief Creates a builder.
|
||||
*
|
||||
* \param[in] func The function being targeted.
|
||||
* \param[in] cursor The cursor.
|
||||
* \return The builder.
|
||||
*/
|
||||
static pco_builder pco_builder_create(pco_func *func, pco_cursor cursor)
|
||||
{
|
||||
return (pco_builder){
|
||||
.func = func,
|
||||
.cursor = cursor,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Inserts a block at a position specified by the builder.
|
||||
*
|
||||
* \param[in] b The builder.
|
||||
* \param[in] block The block.
|
||||
*/
|
||||
/* TODO: test with multiple blocks. */
|
||||
static inline void pco_builder_insert_block(pco_builder *b, pco_block *block)
|
||||
{
|
||||
struct list_head *list = &pco_cursor_cf_node(b->cursor)->link;
|
||||
bool before = pco_cursor_is_before(b->cursor);
|
||||
|
||||
list_add(&block->cf_node.link, before ? list->prev : list);
|
||||
b->cursor = pco_cursor_after_block(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Inserts a instruction at a position specified by the builder.
|
||||
*
|
||||
* \param[in] b The builder.
|
||||
* \param[in] instr The instruction.
|
||||
*/
|
||||
static inline void pco_builder_insert_instr(pco_builder *b, pco_instr *instr)
|
||||
{
|
||||
pco_instr *cursor_instr = pco_cursor_instr(b->cursor);
|
||||
bool before = pco_cursor_is_before(b->cursor);
|
||||
pco_block *block = pco_cursor_block(b->cursor);
|
||||
struct list_head *list = cursor_instr ? &cursor_instr->link : &block->instrs;
|
||||
|
||||
instr->parent_block = block;
|
||||
|
||||
list_add(&instr->link, (before && cursor_instr) ? list->prev : list);
|
||||
b->cursor = pco_cursor_after_instr(instr);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Inserts a instruction group at a position specified by the builder.
|
||||
*
|
||||
* \param[in] b The builder.
|
||||
* \param[in] igrp The instruction group.
|
||||
*/
|
||||
static inline void pco_builder_insert_igrp(pco_builder *b, pco_igrp *igrp)
|
||||
{
|
||||
pco_igrp *cursor_igrp = pco_cursor_igrp(b->cursor);
|
||||
bool before = pco_cursor_is_before(b->cursor);
|
||||
pco_block *block = pco_cursor_block(b->cursor);
|
||||
struct list_head *list = cursor_igrp ? &cursor_igrp->link : &block->instrs;
|
||||
|
||||
igrp->parent_block = block;
|
||||
|
||||
list_add(&igrp->link, (before && cursor_igrp) ? list->prev : list);
|
||||
b->cursor = pco_cursor_after_igrp(igrp);
|
||||
}
|
||||
|
||||
/* Generated op building functions. */
|
||||
#include "pco_builder_ops.h"
|
||||
|
||||
#endif /* PCO_BUILDER_H */
|
||||
@@ -0,0 +1,122 @@
|
||||
# Copyright © 2024 Imagination Technologies Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from mako.template import Template
|
||||
from pco_ops import *
|
||||
|
||||
template = """/*
|
||||
* Copyright © 2024 Imagination Technologies Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef PCO_BUILDER_OPS_H
|
||||
#define PCO_BUILDER_OPS_H
|
||||
|
||||
/**
|
||||
* \\file pco_builder_ops.h
|
||||
*
|
||||
* \\brief PCO op building functions.
|
||||
*/
|
||||
|
||||
#include "pco_internal.h"
|
||||
#include "pco_common.h"
|
||||
#include "pco_ops.h"
|
||||
#include "util/macros.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/** Op mod getting/setting. */
|
||||
static inline
|
||||
bool pco_instr_has_mod(const pco_instr *instr, enum pco_op_mod mod)
|
||||
{
|
||||
assert(mod < _PCO_OP_MOD_COUNT);
|
||||
return (pco_op_info[instr->op].mods & (1ULL << mod)) != 0;
|
||||
}
|
||||
|
||||
static inline
|
||||
void pco_instr_set_mod(pco_instr *instr, enum pco_op_mod mod, uint32_t val)
|
||||
{
|
||||
assert(mod < _PCO_OP_MOD_COUNT);
|
||||
unsigned mod_index = pco_op_info[instr->op].mod_map[mod];
|
||||
assert(mod_index > 0);
|
||||
instr->mod[mod_index - 1] = val;
|
||||
}
|
||||
|
||||
static inline
|
||||
uint32_t pco_instr_get_mod(pco_instr *instr, enum pco_op_mod mod)
|
||||
{
|
||||
assert(mod < _PCO_OP_MOD_COUNT);
|
||||
unsigned mod_index = pco_op_info[instr->op].mod_map[mod];
|
||||
assert(mod_index > 0);
|
||||
return instr->mod[mod_index - 1];
|
||||
}
|
||||
|
||||
% for t, cname, ctype in op_mods.values():
|
||||
static inline
|
||||
bool pco_instr_has_${t.tname}(const pco_instr *instr)
|
||||
{
|
||||
return pco_instr_has_mod(instr, ${cname});
|
||||
}
|
||||
|
||||
static inline
|
||||
void pco_instr_set_${t.tname}(pco_instr *instr, ${t.name} val)
|
||||
{
|
||||
return pco_instr_set_mod(instr, ${cname}, val);
|
||||
}
|
||||
|
||||
static inline
|
||||
${t.name} pco_instr_get_${t.tname}(pco_instr *instr)
|
||||
{
|
||||
return pco_instr_get_mod(instr, ${cname});
|
||||
}
|
||||
|
||||
% endfor
|
||||
% for op in ops.values():
|
||||
% if bool(op.op_mods):
|
||||
struct ${op.bname}_mods {
|
||||
% for t, *_ in op.op_mods:
|
||||
${t.name} ${t.tname};
|
||||
% endfor
|
||||
};
|
||||
% endif
|
||||
#define ${op.bname}(${op.builder_params[1]}${op.builder_params[2]}) _${op.bname}(${op.builder_params[1]}${op.builder_params[3]})
|
||||
static
|
||||
pco_instr *_${op.bname}(${op.builder_params[0]})
|
||||
{
|
||||
pco_instr *instr = pco_instr_create(pco_cursor_func(b->cursor),
|
||||
${op.cname.upper()},
|
||||
${op.num_dests},
|
||||
${op.num_srcs});
|
||||
|
||||
% if op.has_target_cf_node:
|
||||
instr->target_cf_node = target_cf_node;
|
||||
% endif
|
||||
% for d in range(op.num_dests):
|
||||
instr->dest[${d}] = dest${d};
|
||||
% endfor
|
||||
% for s in range(op.num_srcs):
|
||||
instr->src[${s}] = src${s};
|
||||
% endfor
|
||||
|
||||
% for t, *_ in op.op_mods:
|
||||
% if t.nzdefault is None:
|
||||
pco_instr_set_${t.tname}(instr, mods.${t.tname});
|
||||
% else:
|
||||
pco_instr_set_${t.tname}(instr, !mods.${t.tname} ? ${t.nzdefault} : mods.${t.tname});
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
pco_builder_insert_instr(b, instr);
|
||||
return instr;
|
||||
}
|
||||
|
||||
% endfor
|
||||
#endif /* PCO_BUILDER_OPS_H */"""
|
||||
|
||||
def main():
|
||||
print(Template(template).render(ops=ops, op_mods=op_mods))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -19,7 +19,10 @@
|
||||
#include "pco_ops.h"
|
||||
#include "spirv/nir_spirv.h"
|
||||
#include "util/macros.h"
|
||||
#include "util/list.h"
|
||||
#include "util/u_dynarray.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -71,6 +74,267 @@ extern const char *pco_skip_passes;
|
||||
extern bool pco_color;
|
||||
|
||||
void pco_debug_init(void);
|
||||
|
||||
typedef struct _pco_cf_node pco_cf_node;
|
||||
typedef struct _pco_func pco_func;
|
||||
typedef struct _pco_block pco_block;
|
||||
typedef struct _pco_instr pco_instr;
|
||||
|
||||
/** PCO reference index. */
|
||||
typedef struct PACKED _pco_ref {
|
||||
/** Reference value. */
|
||||
union PACKED {
|
||||
unsigned val : 32;
|
||||
|
||||
struct PACKED {
|
||||
unsigned num : 2; /** Index register number. */
|
||||
unsigned offset : 8; /** Offset. */
|
||||
unsigned _pad : 22;
|
||||
} idx_reg;
|
||||
};
|
||||
|
||||
/** Source/destination modifiers. */
|
||||
bool oneminus : 1;
|
||||
bool clamp : 1;
|
||||
bool abs : 1;
|
||||
bool neg : 1;
|
||||
bool flr : 1;
|
||||
enum pco_elem elem : 4; /** .e0.e1.e2.e3 */
|
||||
|
||||
enum pco_dtype dtype : 2; /** Reference data-type. */
|
||||
unsigned chans : 10; /** Number of channels (1-1024). */
|
||||
enum pco_bits bits : 3; /** Bit width. */
|
||||
enum pco_ref_type type : 3; /** Reference type. */
|
||||
enum pco_reg_class reg_class : 4; /** Register class. */
|
||||
|
||||
unsigned _pad : 1;
|
||||
} pco_ref;
|
||||
static_assert(sizeof(pco_ref) == 8, "sizeof(pco_ref) != 8");
|
||||
|
||||
/** PCO phi source. */
|
||||
typedef struct _pco_phi_src {
|
||||
struct list_head link; /** Link in pco_instr::phi_srcs. */
|
||||
|
||||
pco_block *pred; /** Predecessor block. */
|
||||
pco_ref ref; /** Source reference. */
|
||||
} pco_phi_src;
|
||||
|
||||
/** PCO instruction group. */
|
||||
typedef struct _pco_igrp {
|
||||
struct list_head link; /** Link in pco_block::instrs. */
|
||||
pco_block *parent_block; /** Basic block containing the igrp. */
|
||||
pco_func *parent_func; /** Parent function. */
|
||||
|
||||
pco_instr *instrs[_PCO_OP_PHASE_COUNT]; /** Instruction/group list. */
|
||||
|
||||
/** Instruction group header. */
|
||||
struct {
|
||||
unsigned da;
|
||||
unsigned length;
|
||||
union {
|
||||
enum pco_oporg oporg;
|
||||
enum pco_opcnt opcnt;
|
||||
};
|
||||
bool olchk;
|
||||
bool w1p;
|
||||
bool w0p;
|
||||
enum pco_cc cc;
|
||||
enum pco_alutype alutype;
|
||||
union {
|
||||
struct {
|
||||
bool end;
|
||||
bool atom;
|
||||
unsigned rpt;
|
||||
};
|
||||
struct {
|
||||
unsigned miscctl;
|
||||
enum pco_ctrlop ctrlop;
|
||||
};
|
||||
};
|
||||
} hdr;
|
||||
|
||||
struct {
|
||||
pco_ref s0;
|
||||
pco_ref s1;
|
||||
pco_ref s2;
|
||||
|
||||
pco_ref s3;
|
||||
pco_ref s4;
|
||||
pco_ref s5;
|
||||
} srcs;
|
||||
|
||||
struct {
|
||||
pco_ref is0;
|
||||
pco_ref is1;
|
||||
pco_ref is2;
|
||||
pco_ref is3;
|
||||
pco_ref is4;
|
||||
pco_ref is5;
|
||||
} iss;
|
||||
|
||||
struct {
|
||||
pco_ref w0;
|
||||
pco_ref w1;
|
||||
} dests;
|
||||
|
||||
struct {
|
||||
enum pco_igrp_hdr_variant igrp_hdr;
|
||||
union {
|
||||
enum pco_main_variant main;
|
||||
enum pco_backend_variant backend;
|
||||
enum pco_bitwise_variant bitwise;
|
||||
enum pco_ctrl_variant ctrl;
|
||||
} instr[_PCO_OP_PHASE_COUNT];
|
||||
enum pco_src_variant lower_src;
|
||||
enum pco_src_variant upper_src;
|
||||
enum pco_iss_variant iss;
|
||||
enum pco_dst_variant dest;
|
||||
} variant;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
unsigned hdr;
|
||||
unsigned lower_srcs;
|
||||
unsigned upper_srcs;
|
||||
unsigned iss;
|
||||
unsigned dests;
|
||||
unsigned instrs[_PCO_OP_PHASE_COUNT];
|
||||
unsigned word_padding;
|
||||
unsigned align_padding;
|
||||
unsigned total;
|
||||
} len;
|
||||
|
||||
unsigned offset;
|
||||
} enc;
|
||||
|
||||
unsigned index; /** Igrp index. */
|
||||
char *comment; /** Comment string. */
|
||||
|
||||
} pco_igrp;
|
||||
|
||||
/** PCO instruction. */
|
||||
typedef struct _pco_instr {
|
||||
union {
|
||||
struct {
|
||||
struct list_head link; /** Link in pco_block::instrs. */
|
||||
pco_block *parent_block; /** Basic block containing the instruction. */
|
||||
};
|
||||
|
||||
pco_igrp *parent_igrp; /** Igrp containing the instruction. */
|
||||
};
|
||||
|
||||
pco_func *parent_func; /** Parent function. */
|
||||
|
||||
enum pco_op op;
|
||||
|
||||
unsigned num_dests;
|
||||
pco_ref *dest;
|
||||
unsigned num_srcs;
|
||||
pco_ref *src;
|
||||
|
||||
union {
|
||||
struct list_head phi_srcs;
|
||||
pco_cf_node *target_cf_node;
|
||||
};
|
||||
|
||||
/** Instruction flags/modifiers. */
|
||||
uint32_t mod[_PCO_OP_MAX_MODS];
|
||||
|
||||
unsigned index; /** Instruction index. */
|
||||
char *comment; /** Comment string. */
|
||||
} pco_instr;
|
||||
|
||||
/** PCO control-flow node type. */
|
||||
enum pco_cf_node_type {
|
||||
PCO_CF_NODE_TYPE_BLOCK,
|
||||
PCO_CF_NODE_TYPE_IF,
|
||||
PCO_CF_NODE_TYPE_LOOP,
|
||||
PCO_CF_NODE_TYPE_FUNC,
|
||||
};
|
||||
|
||||
/** PCO control-flow node. */
|
||||
typedef struct _pco_cf_node {
|
||||
struct list_head link; /** Link in lists of pco_cf_nodes. */
|
||||
enum pco_cf_node_type type; /** CF node type. */
|
||||
struct _pco_cf_node *parent; /** Parent cf node. */
|
||||
} pco_cf_node;
|
||||
|
||||
/** PCO basic block. */
|
||||
typedef struct _pco_block {
|
||||
pco_cf_node cf_node; /** Control flow node. */
|
||||
pco_func *parent_func; /** Parent function. */
|
||||
struct list_head instrs; /** Instruction/group list. */
|
||||
unsigned index; /** Block index. */
|
||||
} pco_block;
|
||||
|
||||
/** PCO if cf construct. */
|
||||
typedef struct _pco_if {
|
||||
pco_cf_node cf_node; /** CF node. */
|
||||
pco_func *parent_func; /** Parent function. */
|
||||
pco_ref cond; /** If condition. */
|
||||
struct list_head then_body; /** List of pco_cf_nodes for if body. */
|
||||
struct list_head else_body; /** List of pco_cf_nodes for else body. */
|
||||
unsigned index; /** If index. */
|
||||
} pco_if;
|
||||
|
||||
/** PCO loop cf construct. */
|
||||
typedef struct _pco_loop {
|
||||
pco_cf_node cf_node; /** CF node. */
|
||||
pco_func *parent_func; /** Parent function. */
|
||||
struct list_head body; /** List of pco_cf_nodes for loop body. */
|
||||
unsigned index; /** Loop index. */
|
||||
} pco_loop;
|
||||
|
||||
/** PCO function. */
|
||||
typedef struct _pco_func {
|
||||
struct list_head link; /** Link in pco_shader::funcs. */
|
||||
pco_cf_node cf_node; /** Control flow node. */
|
||||
|
||||
pco_shader *parent_shader; /** Shader containing the function. */
|
||||
|
||||
bool is_entrypoint;
|
||||
unsigned index; /** Function index. */
|
||||
const char *name; /** Function name. */
|
||||
|
||||
struct list_head body; /** List of pco_cf_nodes for function body. */
|
||||
|
||||
unsigned num_params;
|
||||
pco_ref *params;
|
||||
|
||||
unsigned next_ssa; /** Next SSA node index. */
|
||||
unsigned next_instr; /** Next instruction index. */
|
||||
unsigned next_igrp; /** Next igrp index. */
|
||||
unsigned next_block; /** Next block index. */
|
||||
unsigned next_if; /** Next if index. */
|
||||
unsigned next_loop; /** Next loop index. */
|
||||
|
||||
unsigned temps; /** Number of temps allocated. */
|
||||
} pco_func;
|
||||
|
||||
/** PCO shader. */
|
||||
typedef struct _pco_shader {
|
||||
pco_ctx *ctx; /** Compiler context. */
|
||||
nir_shader *nir; /** Source NIR shader. */
|
||||
|
||||
gl_shader_stage stage; /** Shader stage. */
|
||||
const char *name; /** Shader name. */
|
||||
bool is_internal; /** Whether this is an internal shader. */
|
||||
bool is_grouped; /** Whether the shader uses igrps. */
|
||||
|
||||
struct list_head funcs; /** List of functions. */
|
||||
unsigned next_func; /** Next function index. */
|
||||
|
||||
struct {
|
||||
struct util_dynarray buf; /** Shader binary. */
|
||||
|
||||
/** Binary patch info. */
|
||||
unsigned num_patches;
|
||||
struct {
|
||||
unsigned offset;
|
||||
} * patch;
|
||||
} binary;
|
||||
} pco_shader;
|
||||
|
||||
/** Op info. */
|
||||
struct pco_op_info {
|
||||
const char *str; /** Op name string. */
|
||||
@@ -108,4 +372,214 @@ struct pco_ref_mod_info {
|
||||
};
|
||||
extern const struct pco_ref_mod_info pco_ref_mod_info[_PCO_REF_MOD_COUNT];
|
||||
|
||||
pco_instr *pco_instr_create(pco_func *func,
|
||||
enum pco_op op,
|
||||
unsigned num_dests,
|
||||
unsigned num_srcs);
|
||||
|
||||
/* Cast helpers. */
|
||||
|
||||
/* CF nodes. */
|
||||
#define PCO_DEFINE_CAST(name, in_type, out_type, field, type_field, type_value) \
|
||||
static inline out_type *name(const in_type *parent) \
|
||||
{ \
|
||||
assert(parent && parent->type_field == type_value); \
|
||||
return list_entry(parent, out_type, field); \
|
||||
}
|
||||
|
||||
PCO_DEFINE_CAST(pco_cf_node_as_block,
|
||||
pco_cf_node,
|
||||
pco_block,
|
||||
cf_node,
|
||||
type,
|
||||
PCO_CF_NODE_TYPE_BLOCK)
|
||||
PCO_DEFINE_CAST(pco_cf_node_as_if,
|
||||
pco_cf_node,
|
||||
pco_if,
|
||||
cf_node,
|
||||
type,
|
||||
PCO_CF_NODE_TYPE_IF)
|
||||
PCO_DEFINE_CAST(pco_cf_node_as_loop,
|
||||
pco_cf_node,
|
||||
pco_loop,
|
||||
cf_node,
|
||||
type,
|
||||
PCO_CF_NODE_TYPE_LOOP)
|
||||
PCO_DEFINE_CAST(pco_cf_node_as_func,
|
||||
pco_cf_node,
|
||||
pco_func,
|
||||
cf_node,
|
||||
type,
|
||||
PCO_CF_NODE_TYPE_FUNC)
|
||||
|
||||
/**
|
||||
* \brief Returns the entrypoint function of a PCO shader.
|
||||
*
|
||||
* \param[in] shader The PCO shader.
|
||||
* \return The entrypoint function, or NULL if the shader has no functions.
|
||||
*/
|
||||
static inline pco_func *pco_entrypoint(pco_shader *shader)
|
||||
{
|
||||
if (list_is_empty(&shader->funcs))
|
||||
return NULL;
|
||||
|
||||
pco_func *func = list_first_entry(&shader->funcs, pco_func, link);
|
||||
assert(func->is_entrypoint);
|
||||
return func;
|
||||
}
|
||||
|
||||
/* Motions. */
|
||||
/**
|
||||
* \brief Returns the first block in a function.
|
||||
*
|
||||
* \param[in] func The function.
|
||||
* \return The first block, or NULL if the function body is empty.
|
||||
*/
|
||||
static inline pco_block *pco_first_block(pco_func *func)
|
||||
{
|
||||
pco_cf_node *cf_node = list_first_entry(&func->body, pco_cf_node, link);
|
||||
if (list_is_empty(&func->body))
|
||||
return NULL;
|
||||
|
||||
return pco_cf_node_as_block(cf_node);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the last block in a function.
|
||||
*
|
||||
* \param[in] func The function.
|
||||
* \return The last block, or NULL if the function body is empty.
|
||||
*/
|
||||
static inline pco_block *pco_last_block(pco_func *func)
|
||||
{
|
||||
pco_cf_node *cf_node = list_last_entry(&func->body, pco_cf_node, link);
|
||||
if (list_is_empty(&func->body))
|
||||
return NULL;
|
||||
|
||||
return pco_cf_node_as_block(cf_node);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the first instruction in a block.
|
||||
*
|
||||
* \param[in] block The block.
|
||||
* \return The first instruction, or NULL if the block is empty.
|
||||
*/
|
||||
static inline pco_instr *pco_first_instr(pco_block *block)
|
||||
{
|
||||
assert(!block->parent_func->parent_shader->is_grouped);
|
||||
if (list_is_empty(&block->instrs))
|
||||
return NULL;
|
||||
|
||||
return list_first_entry(&block->instrs, pco_instr, link);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the last instruction in a block.
|
||||
*
|
||||
* \param[in] block The block.
|
||||
* \return The last instruction, or NULL if the block is empty.
|
||||
*/
|
||||
static inline pco_instr *pco_last_instr(pco_block *block)
|
||||
{
|
||||
assert(!block->parent_func->parent_shader->is_grouped);
|
||||
if (list_is_empty(&block->instrs))
|
||||
return NULL;
|
||||
|
||||
return list_last_entry(&block->instrs, pco_instr, link);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the next instruction.
|
||||
*
|
||||
* \param[in] instr The current instruction.
|
||||
* \return The next instruction, or NULL if the end of the block has been
|
||||
* reached.
|
||||
*/
|
||||
static inline pco_instr *pco_next_instr(pco_instr *instr)
|
||||
{
|
||||
assert(!instr->parent_func->parent_shader->is_grouped);
|
||||
if (!instr || instr == pco_last_instr(instr->parent_block))
|
||||
return NULL;
|
||||
|
||||
return list_entry(instr->link.next, pco_instr, link);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the previous instruction.
|
||||
*
|
||||
* \param[in] instr The current instruction.
|
||||
* \return The previous instruction, or NULL if the start of the block has been
|
||||
* reached.
|
||||
*/
|
||||
static inline pco_instr *pco_prev_instr(pco_instr *instr)
|
||||
{
|
||||
assert(!instr->parent_func->parent_shader->is_grouped);
|
||||
if (!instr || instr == pco_first_instr(instr->parent_block))
|
||||
return NULL;
|
||||
|
||||
return list_entry(instr->link.prev, pco_instr, link);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the first instruction group in a block.
|
||||
*
|
||||
* \param[in] block The block.
|
||||
* \return The first instruction group, or NULL if the block is empty.
|
||||
*/
|
||||
static inline pco_igrp *pco_first_igrp(pco_block *block)
|
||||
{
|
||||
assert(block->parent_func->parent_shader->is_grouped);
|
||||
if (list_is_empty(&block->instrs))
|
||||
return NULL;
|
||||
|
||||
return list_first_entry(&block->instrs, pco_igrp, link);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the last instruction group in a block.
|
||||
*
|
||||
* \param[in] block The block.
|
||||
* \return The last instruction group, or NULL if the block is empty.
|
||||
*/
|
||||
static inline pco_igrp *pco_last_igrp(pco_block *block)
|
||||
{
|
||||
assert(block->parent_func->parent_shader->is_grouped);
|
||||
if (list_is_empty(&block->instrs))
|
||||
return NULL;
|
||||
|
||||
return list_last_entry(&block->instrs, pco_igrp, link);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the next instruction group.
|
||||
*
|
||||
* \param[in] igrp The current instruction group.
|
||||
* \return The next instruction group, or NULL if the end of the block has been
|
||||
* reached.
|
||||
*/
|
||||
static inline pco_igrp *pco_next_igrp(pco_igrp *igrp)
|
||||
{
|
||||
assert(igrp->parent_func->parent_shader->is_grouped);
|
||||
if (!igrp || igrp == pco_last_igrp(igrp->parent_block))
|
||||
return NULL;
|
||||
|
||||
return list_entry(igrp->link.next, pco_igrp, link);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the previous instruction group.
|
||||
*
|
||||
* \param[in] igrp The current instruction group.
|
||||
* \return The previous instruction group, or NULL if the start of the block has
|
||||
* been reached.
|
||||
*/
|
||||
static inline pco_igrp *pco_prev_igrp(pco_igrp *igrp)
|
||||
{
|
||||
assert(igrp->parent_func->parent_shader->is_grouped);
|
||||
if (!igrp || igrp == pco_first_igrp(igrp->parent_block))
|
||||
return NULL;
|
||||
|
||||
return list_entry(igrp->link.prev, pco_igrp, link);
|
||||
}
|
||||
#endif /* PCO_INTERNAL_H */
|
||||
|
||||
@@ -249,4 +249,8 @@ OM_PHASE2END = op_mod('phase2end', BaseType.bool)
|
||||
# HW ops
|
||||
O_NOP = hw_op('nop', [OM_EXEC_CND, OM_END])
|
||||
|
||||
O_WOP = hw_op('wop')
|
||||
|
||||
O_MBYP0 = hw_op('mbyp0', [OM_OLCHK, OM_EXEC_CND, OM_END, OM_ATOM, OM_RPT], 1, 1, [], [[RM_ABS, RM_NEG]])
|
||||
|
||||
O_FADD = hw_op('fadd', [OM_LP, OM_SAT], 1, 2, [], [[RM_ABS, RM_NEG, RM_FLR], [RM_ABS]])
|
||||
|
||||
@@ -16,8 +16,9 @@ class BaseType(Enum):
|
||||
enum = auto()
|
||||
|
||||
class Type(object):
|
||||
def __init__(self, name, base_type, num_bits, dec_bits, check, encode, nzdefault, print_early, enum):
|
||||
def __init__(self, name, tname, base_type, num_bits, dec_bits, check, encode, nzdefault, print_early, enum):
|
||||
self.name = name
|
||||
self.tname = tname
|
||||
self.base_type = base_type
|
||||
self.num_bits = num_bits
|
||||
self.dec_bits = dec_bits
|
||||
@@ -41,7 +42,7 @@ def type(name, base_type, num_bits=None, dec_bits=None, check=None, encode=None,
|
||||
else:
|
||||
assert False, f'Invalid base type for type {name}.'
|
||||
|
||||
t = Type(_name, base_type, num_bits, dec_bits, check, encode, nzdefault, print_early, enum)
|
||||
t = Type(_name, name, base_type, num_bits, dec_bits, check, encode, nzdefault, print_early, enum)
|
||||
types[name] = t
|
||||
return t
|
||||
|
||||
@@ -360,12 +361,11 @@ def bit_struct(name, bit_set, field_mappings, data=None):
|
||||
return bs
|
||||
|
||||
# Op definitions.
|
||||
VARIABLE = ~0
|
||||
|
||||
class Op(object):
|
||||
def __init__(self, name, cname, is_pseudo, op_mods, cop_mods, op_mod_map, num_dests, num_srcs, dest_mods, cdest_mods, src_mods, csrc_mods, has_target_cf_node):
|
||||
def __init__(self, name, cname, bname, is_pseudo, op_mods, cop_mods, op_mod_map, num_dests, num_srcs, dest_mods, cdest_mods, src_mods, csrc_mods, has_target_cf_node, builder_params):
|
||||
self.name = name
|
||||
self.cname = cname
|
||||
self.bname = bname
|
||||
self.is_pseudo = is_pseudo
|
||||
self.op_mods = op_mods
|
||||
self.cop_mods = cop_mods
|
||||
@@ -377,6 +377,7 @@ class Op(object):
|
||||
self.src_mods = src_mods
|
||||
self.csrc_mods = csrc_mods
|
||||
self.has_target_cf_node = has_target_cf_node
|
||||
self.builder_params = builder_params
|
||||
|
||||
ops = {}
|
||||
|
||||
@@ -384,11 +385,34 @@ def op(name, is_pseudo, op_mods, num_dests, num_srcs, dest_mods, src_mods, has_t
|
||||
assert name not in ops.keys(), f'Duplicate op "{name}".'
|
||||
|
||||
cname = f'{prefix}_op_{name}'.replace('.', '_')
|
||||
bname = f'{prefix}_{name}'.replace('.', '_')
|
||||
cop_mods = 0 if not op_mods else ' | '.join([f'(1 << {mod[1]})' for mod in op_mods])
|
||||
op_mod_map = {mod[1]: index + 1 for index, mod in enumerate(op_mods)}
|
||||
cdest_mods = {i: 0 if not dest_mods else ' | '.join([f'(1 << {mod[1]})' for mod in destn_mods]) for i, destn_mods in enumerate(dest_mods)}
|
||||
csrc_mods = {i: 0 if not src_mods else ' | '.join([f'(1 << {mod[1]})' for mod in srcn_mods]) for i, srcn_mods in enumerate(src_mods)}
|
||||
op = Op(name, cname, is_pseudo, op_mods, cop_mods, op_mod_map, num_dests, num_srcs, dest_mods, cdest_mods, src_mods, csrc_mods, has_target_cf_node)
|
||||
|
||||
# Typed and untyped params for builder.
|
||||
builder_params = ['', '', '', '']
|
||||
builder_params[0] += 'pco_builder *b'
|
||||
builder_params[1] += 'b'
|
||||
if has_target_cf_node:
|
||||
builder_params[0] += ', pco_cf_node *target_cf_node'
|
||||
builder_params[1] += ', target_cf_node'
|
||||
|
||||
for d in range(num_dests):
|
||||
builder_params[0] += f', pco_ref dest{d}'
|
||||
builder_params[1] += f', dest{d}'
|
||||
|
||||
for s in range(num_srcs):
|
||||
builder_params[0] += f', pco_ref src{s}'
|
||||
builder_params[1] += f', src{s}'
|
||||
|
||||
if bool(op_mods):
|
||||
builder_params[0] += f', struct {prefix}_{name}_mods mods'
|
||||
builder_params[2] = ', ...'
|
||||
builder_params[3] = f', (struct {bname}_mods){{0, ##__VA_ARGS__}}'
|
||||
|
||||
op = Op(name, cname, bname, is_pseudo, op_mods, cop_mods, op_mod_map, num_dests, num_srcs, dest_mods, cdest_mods, src_mods, csrc_mods, has_target_cf_node, builder_params)
|
||||
ops[name] = op
|
||||
return op
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
#include "pco.h"
|
||||
#include "pco_builder.h"
|
||||
#include "pco_internal.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
Reference in New Issue
Block a user