Mentions légales du service

Skip to content
Snippets Groups Projects
parameters.c 23.00 KiB
/**
 *
 * @file parameters.c
 *
 * @copyright 2019-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 ***
 *
 * @brief Chameleon auxiliary routines for testing structures
 *
 * @version 1.3.0
 * @author Lucas Barros de Assis
 * @author Mathieu Faverge
 * @author Alycia Lisito
 * @author Lionel Eyraud-Dubois
 * @date 2023-07-05
 *
 */
#include "testings.h"

extern parameter_t parameters[];

void print_usage( const char* prog_name )
{
    parameter_t *param = parameters;

#if !defined(CHAMELEON_TESTINGS_VENDOR)
    if ( CHAMELEON_Initialized() ) {
        int rank = CHAMELEON_Comm_rank();
        if (rank != 0) {
            return;
        }
    }
#endif

    printf( "Usage:\n"
            "  %s -o|--op operation_name [options]\n"
            "  %s -f|--file input_file [options]\n",
            prog_name, prog_name );

    while( param->helper != NULL )
    {
        char str[STR_MAX_LENGTH];

        /* This is not an option, we skip it */
        if ( !(param->flags & PARAM_OPTION) ) {
            param++;
            continue;
        }

        /* This is an option header */
        if ( param->name == NULL ) {
            printf( "\n  %s:\n", param->helper );
            param++;
            continue;
        }

        if ( param->shname > 0 ) {
            snprintf( str, STR_MAX_LENGTH, "-%c, --%s",
                      param->shname, param->name );
        }
        else {
            snprintf( str, STR_MAX_LENGTH, "    --%s",
                      param->name );
        }

        /* If an argument is needed, add " x" */
        if ( param->has_arg > 0 ) {
            int len = strlen(str);
            assert( len < (STR_MAX_LENGTH-3) );

            str[ len   ] = ' ';
            str[ len+1 ] = 'x';
            str[ len+2 ] = '\0';
        }
        printf( "    %-23s %s\n",
                str, param->helper );
        param++;
    }

    printf( "\n"
            "For example: %s -H -o gemm -t 2 -m 2000 -n 2000 -k 2000 -b 200\n"
            "  will run one gemm with three matrices of size 2000x2000 each and a tile size of 200.\n"
            "  The output will be in the human readable format\n"
            "\n", prog_name );
#if !defined(CHAMELEON_TESTINGS_VENDOR)
    printf( "Remarks about timing:\n"
            "  Timings are reported respectively as 'tsub' for the graph submission time, and 'time'\n"
            "  for the execution time.\n"
            "  By default the synchronous tile interface is used to perform the timings. 'tsub' is null.\n"
            "  If the --async option is enabled, then the asynchronous interface is called. 'tsub' reports\n"
            "  the task submission time, and 'time' the execution time that includes 'tsub'.\n"
            "  If the --splitsub option is enabled, then the asynchronous interface is called and task\n"
            "  submission is fully performed before starting the computation. 'tsub' reports the\n"
            "  task submission time, and 'time' the execution time excluding 'tsub'.\n"
            "  Note that the 'gflops' field is always computed with 'time'\n" );
#endif
}

/**
 ********************************************************************************
 *
 * @brief Get the list of values associated to a given parameter
 *
 *******************************************************************************
 *
 * @param[in] name
 *          The name of the parameter we are interested in.
 *
 * @return NULL if no parameter exists with this name, otherwise the pointer to
 * the list of values associated to this parameter.
 *
 *******************************************************************************
 */
vallist_t *
parameters_getlist( const char *name )
{
    parameter_t *param = parameters_getbyname( name );
    if ( param == NULL ) {
        return NULL;
    }
    else {
        return param->vallist;
    }
}

/**
 ********************************************************************************
 *
 * @brief Parses a list in form A1, A2, ..., An and insert the values in an
 * argument list.
 *
 *******************************************************************************
 *
 * @param[inout] param
 *          The parameter associated to the list.
 *          On exit, the list of values are added to the parameter list of
 *          possible values.
 *
 * @param[in] liststr
 *          The string that holds the list
 *
 *******************************************************************************
 */
void
parameters_read_list( parameter_t *param,
                      const char  *liststr )
{
    const char *delim = ", \n";
    char *str = strdup( liststr );
    char *token, *saveptr;
    vallist_t *previous, *current;

    /* Initialize the list items */
    previous = NULL;
    current  = param->vallist;

    /* Move to the end of the list if some parameters have already been registered */
    while( current != NULL ) {
        previous = current;
        current  = current->next;
    }
    assert( current == NULL );

    token = strtok_r( str, delim, &saveptr );
    while ( token != NULL ) {
        current = calloc( 1, sizeof(vallist_t) );

        /* Read the value */
        current->value = param->read( token );

        /* Insert at the end of the list */
        if ( previous != NULL ) {
            previous->next = current;
        }
        else {
            /* Nothing was in the list */
            param->vallist = current;
        }

        previous = current;

        /* Move to the next token */
        token = strtok_r( NULL, delim, &saveptr );
    }

    free( str );
}

/**
 ********************************************************************************
 *
 * @brief Parses a list in form start:end[:step] and inserts the values in an
 * argument list.
 *
 *******************************************************************************
 *
 * @param[inout] param
 *          The parameter associated to the list.
 *          On exit, the range of values are added to the parameter list of
 *          possible values.
 *
 * @param[in] rangestr
 *          The string that holds the range
 *
 * @param[in] min
 *          The minimum value available
 *
 * @param[in] max
 *          The maximum value available
 *
 *******************************************************************************
 */
void
parameters_read_intrange( parameter_t *param,
                          const char  *rangestr,
                          int min, int max )
{
    int start, end, step, count;
    vallist_t *previous, *current;

    max = (max == -1) ? INT32_MAX : max;

    count = sscanf( rangestr, "%d:%d:%d", &start, &end, &step );
    if ( count < 2 ) {
        fprintf(stderr, "Incorrect range syntax (%s): data skipped\n", rangestr );
        return;
    }
    else if (count == 2) {
        step = 1;
    }

    /* Check the range */
    if ( (start < min) || (start > max) ||
         (end   < min) || (end   > max) )
    {
        /* Try to shift to 0 to see if now we fit */
        start += min;
        end   += min;
        if ( (start < min) || (start > max) ||
             (end   < min) || (end   > max) )
        {
            fprintf( stderr, "Incorrect range values outside the possible ranges [%d:%d]",
                     min, max );
            if ( min > 0 ) {
                fprintf( stderr, " or [%d:%d]\n", 0, max-min );
            }
            else {
                fprintf( stderr, "\n" );
            }
        }
    }

    /* Initialize the list items */
    previous = NULL;
    current  = param->vallist;

    /* Move to the end of the list if some parameters have already been registered */
    while( current != NULL ) {
        previous = current;
        current  = current->next;
    }
    assert( current == NULL );

    while ( start <= end ) {
        current = calloc( 1, sizeof(vallist_t) );

        /* Read the value */
        current->value.ival = start;

        /* Insert at the end of the list */
        if ( previous != NULL ) {
            previous->next = current;
        }
        else {
            /* Nothing was in the list */
            param->vallist = current;
        }

        previous = current;

        start += step;
    }
}

/**
 ********************************************************************************
 *
 * @brief Wrapper to parse a list or range of values associated to a parameter.
 *
 *******************************************************************************
 *
 * @param[inout] param
 *          The parameter associated to the list.
 *          On exit, the range of values are added to the parameter list of
 *          possible values.
 *
 * @param[in] values
 *          The string that holds the range of list of values
 *
 *******************************************************************************
 */
void
parameters_read( parameter_t *param,
                 const char  *values )
{
    int range;

    if ( values == NULL ) {
        fprintf( stderr, "Error passing NULL parameter to read\n" );
        exit(EXIT_FAILURE);
    }
    range = (values != NULL) && ( strchr( values, ':' ) != NULL );

    /* If we have a ranged of integer values */
    if ( range )
    {
        switch ( param->valtype ) {
        case TestValInt:
            parameters_read_intrange( param, values, 0, -1 );
            break;
        case TestTrans:
            parameters_read_intrange( param, values, ChamNoTrans, ChamConjTrans );
            break;
        case TestUplo:
            parameters_read_intrange( param, values, ChamUpper, ChamUpperLower );
            break;
        case TestDiag:
            parameters_read_intrange( param, values, ChamNonUnit, ChamUnit );
            break;
        case TestSide:
            parameters_read_intrange( param, values, ChamLeft, ChamRight );
            break;
        case TestNormtype:
            parameters_read_intrange( param, values, ChamOneNorm, ChamMaxNorm );
            break;
        default:
            fprintf( stderr, "parameters_read: range is not available for this datatype (%d)\n",
                     param->valtype );
        }
        return;
    }

    parameters_read_list( param, values );
}

/**
 ********************************************************************************
 *
 * @brief Generic function to add value(s) to a given parameter
 *
 *******************************************************************************
 *
 * @param[inout] param
 *          The parameter that will receive the value
 *          On exit, the value(s) (switch, list, range, ...) is/are added to the
 *          parameter list of possible values
 *
 * @param[in] values
 *          The string that holds the values (list, range, NULL if switch)
 *
 *******************************************************************************
 */
void
parameters_addvalues( parameter_t *param,
                      const char  *values )
{
    if ( param->has_arg == 0 ) {
        param->value.ival = 1;
    }
    else if ( param->has_arg == 1 ) {
        param->value = param->read( values );
    }
    else {
        parameters_read( param, values );
    }
}

/**
 ********************************************************************************
 *
 * @brief Parses an input test file.
 *
 *******************************************************************************
 *
 * @param[in] filename
 *          The name of the input file.
 *
 *******************************************************************************
 */
void
parameters_read_file( const char  *filename )
{
    FILE        *fp;
    const char  *delim = " =";
    char        *saveptr;
    char        *line_read, *line;
    char        *name, *values;
    size_t       len = 256;
    parameter_t *param;

    fp = fopen( filename, "r" );
    if ( fp == NULL ) {
        fprintf( stderr, "Error reading input file %s\n", filename );
        perror("fopen");
        exit(1);
    }

    len = 256;
    line_read = malloc( len * sizeof( char ) );

    while ( getline( &line_read, &len, fp ) != -1 )
    {
        line = line_read;

        /* Ignores comments and empty lines */
        if ( (line[0] == '#' ) ||
             (line[0] == '\n') )
        {
            continue;
        }

        /* Removes possible extra spaces */
        while ( line[0] == ' ' ) {
            line++;
        }

        /* Reads the parameter name and values */
        name   = strtok_r( line, delim, &saveptr );
        values = strtok_r( NULL, "",    &saveptr );

        /* Goes for the listed values */
        while ( (values[0] == ' ') ||
                (values[0] == '=') )
        {
            values++;
        }

        param = parameters_getbyname( name );
        if ( param == NULL ) {
            fprintf( stderr, "Parameter %s is not know. We skip it\n", name );
            continue;
        }
        parameters_addvalues( param, values );
    }

    free(line_read);
    fclose(fp);
}

#if !defined(CHAMELEON_TESTINGS_VENDOR)
int
parameters_compute_q( int p )
{
    parameter_t *param;
    int np = CHAMELEON_Comm_size();

    if ( (np % p) != 0 ) {
        fprintf( stderr, "ERROR: The number of processes (%d) must be a multiple of P (%d)\n", np, p );
        exit(1);
    }

    param = parameters_get( 'Q' );
    param->value.ival = np / p;
    return param->value.ival;
}

/**
 ********************************************************************************
 *
 * @brief Helper function to generate the testing descriptors with the right
 * data distrbution.
 *
 *******************************************************************************
 *
 * @param[in] filename
 *          The name of the input file.
 *
 *******************************************************************************
 */
int
parameters_desc_create( const char *id, CHAM_desc_t **descptr, cham_flttype_t dtyp,
                        int mb, int nb, int lm, int ln, int m, int n )
{
    custom_dist_t *custom_args = NULL;
    const char    *custom = parameters_getvalue_str( "custom" );
    intptr_t       mtxfmt = parameters_getvalue_int( "mtxfmt" );
    int            rc;

    mtxfmt = -mtxfmt; /* Inverse sign to get the defined values */

    if ( !custom ) {
        int P = parameters_getvalue_int( "P" );
        int Q = parameters_compute_q( P );
        rc = CHAMELEON_Desc_Create(
            descptr, (void*)mtxfmt, dtyp, mb, nb, mb * nb, lm, ln, 0, 0, m, n, P, Q );
        (*descptr)->name = id;
        return rc;
    }

    if ( ((void*)mtxfmt) == CHAMELEON_MAT_ALLOC_GLOBAL ) {
        fprintf( stderr, "In parameters_desc_create, cannot use custom distributions with global matrix allocation (Use --mtxfmt=1)\n" );
        return CHAMELEON_ERR_ILLEGAL_VALUE;
    }

    rc = chameleon_getrankof_custom_init( &custom_args, custom );
    if ( rc != CHAMELEON_SUCCESS ) {
        return rc;
    }

    rc = CHAMELEON_Desc_Create_User(
        descptr, (void*)mtxfmt, dtyp, mb, nb, mb * nb, lm, ln, 0, 0, m, n, CHAMELEON_Comm_size(), 1,
        NULL, NULL, chameleon_getrankof_custom, custom_args );
    (*descptr)->name = id;
    return rc;
}

/**
 *******************************************************************************
 *
 * @brief Helper function to destroy the testing descriptors.
 *
 *******************************************************************************
 *
 * @param[inout] descptr
 *      The descriptor to destroy. On exit the descriptor can no longer be used.
 *
 *******************************************************************************
 */
int
parameters_desc_destroy(CHAM_desc_t **descptr)
{
    CHAM_desc_t *desc;
    if ( descptr == NULL ) {
        return CHAMELEON_ERR_ILLEGAL_VALUE;
    }
    desc = *descptr;
    if ( desc == NULL ) {
        return CHAMELEON_ERR_ILLEGAL_VALUE;
    }
    if ( desc->get_rankof_init_arg ) {
        if ( desc->get_rankof_init == chameleon_getrankof_custom ) {
            chameleon_getrankof_custom_destroy( (custom_dist_t**)&(desc->get_rankof_init_arg) );
        }
    }
    return CHAMELEON_Desc_Destroy( descptr );
}
#endif

void
parameters_getopt_init( char           *optstring,
                        struct option **longopts )
{
    parameter_t *param = parameters;
    int i;
    int nboptions = 0;
    int strpos = 0;

    while( param->helper != NULL )
    {
        /* This is not an option, we skip it */
        if ( !(param->flags & PARAM_OPTION) ||
             (param->name == NULL) )
        {
            param++;
            continue;
        }

        nboptions++;

        if ( param->shname < 0 ) {
            param++;
            continue;
        }

        optstring[strpos] = param->shname;
        strpos++;
        assert( strpos < STR_MAX_LENGTH );

        if ( param->has_arg > 0 ) {
            optstring[strpos] = ':';
            strpos++;
            assert( strpos < STR_MAX_LENGTH );
        }
        param++;
    }
    optstring[strpos] = '\0';

    /* Now, let's generate the long opt if needed */
#if defined(CHAMELEON_HAVE_GETOPT_LONG)
    if ( longopts != NULL ) {
        struct option *opt;
        *longopts = calloc( nboptions+1, sizeof( struct option ) );

        opt = *longopts;
        param = parameters;

        for ( i=0; i<nboptions; i++, opt++, param++ ) {

            /* Look for a valid option */
            while ( !(param->flags & PARAM_OPTION) ||
                    (param->name == NULL) )
            {
                param++;
            }

            opt->name    = param->name;
            opt->has_arg = ( param->has_arg > 0 ) ? 1 : 0;
            opt->flag    = NULL;
            opt->val     = param->shname;
        }
    }
#endif
}

parameter_t *
parameters_get( int shname )
{
    parameter_t *param = parameters;

    while( param->helper != NULL )
    {
        /* This is not an option, we skip it */
        if ( param->name == NULL ) {
            param++;
            continue;
        }

        if ( shname == param->shname ) {
            return param;
        }
        param++;
    }

    fprintf( stderr, "parameters_get could not find parameter %d(%c)\n", shname, shname );
    return NULL;
}

int
parameters_getvalue_int( const char *name )
{
    parameter_t *param = parameters;

    while( param->helper != NULL )
    {
        /* This is not an option, we skip it */
        if ( param->name == NULL ) {
            param++;
            continue;
        }

        if ( strcasecmp( name, param->name ) != 0 ) {
            param++;
            continue;
        }

        if ( param->has_arg > 1 ) {
            fprintf( stderr, "parameters_getvalue_int should not be called with parameter %s\n", name );
            return -1;
        }

        if ( param->valtype != TestValInt ) {
            fprintf( stderr, "parameters_getvalue_int has been called with a non integer parameter (%s)\n", name );
            return -1;
        }

        return param->value.ival;
    }

    fprintf( stderr, "parameters_getvalue_int could not find parameter %s\n", name );
    return -1;
}

double
parameters_getvalue_fixdbl( const char *name )
{
    parameter_t *param = parameters;

    while( param->helper != NULL )
    {
        /* This is not an option, we skip it */
        if ( param->name == NULL ) {
            param++;
            continue;
        }

        if ( strcasecmp( name, param->name ) != 0 ) {
            param++;
            continue;
        }

        if ( param->has_arg > 1 ) {
            fprintf( stderr, "parameters_getvalue_double should not be called with parameter %s\n", name );
            return -1;
        }

        if ( param->valtype != TestValDouble ) {
            fprintf( stderr, "parameters_getvalue_double has been called with a non float parameter (%s)\n", name );
            return -1;
        }

        return param->value.dval;
    }

    fprintf( stderr, "parameters_getvalue_int could not find parameter %s\n", name );
    return -1;
}

char *
parameters_getvalue_str( const char *name )
{
    parameter_t *param = parameters;

    while( param->helper != NULL )
    {
        /* This is not an option, we skip it */
        if ( param->name == NULL ) {
            param++;
            continue;
        }

        if ( strcasecmp( name, param->name ) != 0 ) {
            param++;
            continue;
        }

        if ( param->has_arg > 1 ) {
            fprintf( stderr, "parameters_getvalue_str should not be called with parameter %s\n", name );
            return NULL;
        }

        if ( param->valtype != TestString ) {
            fprintf( stderr, "parameters_getvalue_str has been called with a non string parameter (%s)\n", name );
            return NULL;
        }

        return param->value.str;
    }

    fprintf( stderr, "parameters_getvalue_str could not find parameter %s\n", name );
    return NULL;
}

parameter_t *
parameters_getbyname( const char *name )
{
    parameter_t *param = parameters;

    while( param->helper != NULL )
    {
        /* This is not an option, we skip it */
        if ( param->name == NULL ) {
            param++;
            continue;
        }

        if ( strcasecmp( name, param->name ) != 0 ) {
            param++;
            continue;
        }

        /* if ( param->has_arg < 2 ) { */
        /*     fprintf( stderr, "parameters_getbyname should not be called with parameter %s\n", name ); */
        /*     return NULL; */
        /* } */

        return param;
    }

    fprintf( stderr, "parameters_getbyname could not find parameter %s\n", name );
    return NULL;
}

void parameters_parser( int argc, char **argv )
{
    int opt;
    char optstring[STR_MAX_LENGTH];
    struct option *longopts = NULL;
    parameter_t *param;

    parameters_getopt_init( optstring, &longopts );

#if defined(CHAMELEON_HAVE_GETOPT_LONG)
    while ((opt = getopt_long(argc, argv, optstring, longopts, NULL)) != -1)
#else
    while ((opt = getopt(argc, argv, optstring)) != -1)
#endif
    {
        switch(opt) {
        case 'h':
            print_usage(argv[0]);
            exit(0);

        case '?': /* error from getopt[_long] */
            exit(1);
            break;

        default:
            param = parameters_get( opt );
            if ( param == NULL ) {
                print_usage(argv[0]);
                exit(1);
            }
            parameters_addvalues( param, optarg );
        }
    }

    if ( longopts != NULL ) {
        free( longopts );
    }

#if !defined(CHAMELEON_TESTINGS_VENDOR)
    /* Force Async if splitsub is enabled */
    {
        int splitsub = parameters_getvalue_int( "splitsub" );

        if ( splitsub ) {
            param = parameters_get( 's' );
            if ( param == NULL ) {
                print_usage(argv[0]);
                exit(1);
            }
            parameters_addvalues( param, NULL );

#if defined(CHAMELEON_RUNTIME_SYNC)
            fprintf( stderr, "Spliting the submission and the execution stages is not possible when the option CHAMELEON_RUNTIME_SYNC is enabled\n" );
            exit(0);
#endif
        }
    }
#endif
}

void
parameters_destroy()
{
    parameter_t *param = parameters;
    vallist_t *current, *next;

    while( param->helper != NULL )
    {
        /* This is not an option, we skip it */
        if ( param->has_arg < 2 ) {
            param++;
            continue;
        }

        current = param->vallist;
        while ( current != NULL )
        {
            next = current->next;
            free( current );
            current = next;
        }
        param++;
    }
    return;
}