glsl: add ast/parser support for subroutine parsing storage (v3.2)

This is the guts of the GLSL parser and AST support for
shader subroutines.

The code creates a subroutine type in the parser, and
uses that there to validate the identifiers. The parser
also distinguishes between subroutine types/function prototypes
/uniforms and subroutine defintions for functions.

Then in the AST conversion it recreates the types, and
stores the subroutine definition info or subroutine info
into the ir_function along with a side lookup table in
the parser state. It also converts subroutine calls into
the enhanced ir_call.

v2: move to handling method calls in
function handling not in field selection.
v3: merge Chris's previous parser patches in here, to
make it clearer what's changed in one place.
v3.1: add more documentation, drop unused include
v3.2: drop is_subroutine_def

Reviewed-by: Chris Forbes <chrisf@ijw.co.nz>
Signed-off-by: Dave Airlie <airlied@redhat.com>
This commit is contained in:
Dave Airlie 2015-06-01 10:55:47 +10:00
parent 884df9ef83
commit 65ac360823
9 changed files with 326 additions and 116 deletions

View file

@ -304,6 +304,16 @@ private:
* Is this function call actually a constructor?
*/
bool cons;
ir_rvalue *
handle_method(exec_list *instructions,
struct _mesa_glsl_parse_state *state);
};
class ast_subroutine_list : public ast_node
{
public:
virtual void print(void) const;
exec_list declarations;
};
class ast_array_specifier : public ast_node {
@ -527,6 +537,12 @@ struct ast_type_qualifier {
/* tess control output layout */
unsigned vertices:1;
/** \} */
/** \name Qualifiers for GL_ARB_shader_subroutine */
/** \{ */
unsigned subroutine:1; /**< Is this marked 'subroutine' */
unsigned subroutine_def:1; /**< Is this marked 'subroutine' with a list of types */
/** \} */
}
/** \brief Set of flags, accessed by name. */
q;
@ -669,6 +685,7 @@ struct ast_type_qualifier {
ast_type_qualifier q,
ast_node* &node);
ast_subroutine_list *subroutine_list;
};
class ast_declarator_list;

View file

@ -26,6 +26,7 @@
#include "glsl_types.h"
#include "ir.h"
#include "main/core.h" /* for MIN2 */
#include "main/shaderobj.h"
static ir_rvalue *
convert_component(ir_rvalue *src, const glsl_type *desired_type);
@ -355,6 +356,8 @@ fix_parameter(void *mem_ctx, ir_rvalue *actual, const glsl_type *formal_type,
static ir_rvalue *
generate_call(exec_list *instructions, ir_function_signature *sig,
exec_list *actual_parameters,
ir_variable *sub_var,
ir_rvalue *array_idx,
struct _mesa_glsl_parse_state *state)
{
void *ctx = state;
@ -421,7 +424,8 @@ generate_call(exec_list *instructions, ir_function_signature *sig,
deref = new(ctx) ir_dereference_variable(var);
}
ir_call *call = new(ctx) ir_call(sig, deref, actual_parameters);
ir_call *call = new(ctx) ir_call(sig, deref, actual_parameters, sub_var, array_idx);
instructions->push_tail(call);
/* Also emit any necessary out-parameter conversions. */
@ -489,6 +493,40 @@ done:
return sig;
}
static ir_function_signature *
match_subroutine_by_name(const char *name,
exec_list *actual_parameters,
struct _mesa_glsl_parse_state *state,
ir_variable **var_r)
{
void *ctx = state;
ir_function_signature *sig = NULL;
ir_function *f, *found = NULL;
const char *new_name;
ir_variable *var;
bool is_exact = false;
new_name = ralloc_asprintf(ctx, "%s_%s", _mesa_shader_stage_to_subroutine_prefix(state->stage), name);
var = state->symbols->get_variable(new_name);
if (!var)
return NULL;
for (int i = 0; i < state->num_subroutine_types; i++) {
f = state->subroutine_types[i];
if (strcmp(f->name, var->type->without_array()->name))
continue;
found = f;
break;
}
if (!found)
return NULL;
*var_r = var;
sig = found->matching_signature(state, actual_parameters,
false, &is_exact);
return sig;
}
static void
print_function_prototypes(_mesa_glsl_parse_state *state, YYLTYPE *loc,
ir_function *f)
@ -1531,6 +1569,65 @@ process_record_constructor(exec_list *instructions,
&actual_parameters, state);
}
ir_rvalue *
ast_function_expression::handle_method(exec_list *instructions,
struct _mesa_glsl_parse_state *state)
{
const ast_expression *field = subexpressions[0];
ir_rvalue *op;
ir_rvalue *result;
void *ctx = state;
/* Handle "method calls" in GLSL 1.20 - namely, array.length() */
YYLTYPE loc = get_location();
state->check_version(120, 300, &loc, "methods not supported");
const char *method;
method = field->primary_expression.identifier;
op = field->subexpressions[0]->hir(instructions, state);
if (strcmp(method, "length") == 0) {
if (!this->expressions.is_empty()) {
_mesa_glsl_error(&loc, state, "length method takes no arguments");
goto fail;
}
if (op->type->is_array()) {
if (op->type->is_unsized_array()) {
_mesa_glsl_error(&loc, state, "length called on unsized array");
goto fail;
}
result = new(ctx) ir_constant(op->type->array_size());
} else if (op->type->is_vector()) {
if (state->ARB_shading_language_420pack_enable) {
/* .length() returns int. */
result = new(ctx) ir_constant((int) op->type->vector_elements);
} else {
_mesa_glsl_error(&loc, state, "length method on matrix only available"
"with ARB_shading_language_420pack");
goto fail;
}
} else if (op->type->is_matrix()) {
if (state->ARB_shading_language_420pack_enable) {
/* .length() returns int. */
result = new(ctx) ir_constant((int) op->type->matrix_columns);
} else {
_mesa_glsl_error(&loc, state, "length method on matrix only available"
"with ARB_shading_language_420pack");
goto fail;
}
} else {
_mesa_glsl_error(&loc, state, "length called on scalar.");
goto fail;
}
} else {
_mesa_glsl_error(&loc, state, "unknown method: `%s'", method);
goto fail;
}
return result;
fail:
return ir_rvalue::error_value(ctx);
}
ir_rvalue *
ast_function_expression::hir(exec_list *instructions,
@ -1543,8 +1640,6 @@ ast_function_expression::hir(exec_list *instructions,
* 2. methods - Only the .length() method of array types.
* 3. functions - Calls to regular old functions.
*
* Method calls are actually detected when the ast_field_selection
* expression is handled.
*/
if (is_constructor()) {
const ast_type_specifier *type = (ast_type_specifier *) subexpressions[0];
@ -1765,11 +1860,22 @@ ast_function_expression::hir(exec_list *instructions,
&actual_parameters,
ctx);
}
} else if (subexpressions[0]->oper == ast_field_selection) {
return handle_method(instructions, state);
} else {
const ast_expression *id = subexpressions[0];
const char *func_name = id->primary_expression.identifier;
const char *func_name;
YYLTYPE loc = get_location();
exec_list actual_parameters;
ir_variable *sub_var = NULL;
ir_rvalue *array_idx = NULL;
if (id->oper == ast_array_index) {
func_name = id->subexpressions[0]->primary_expression.identifier;
array_idx = id->subexpressions[1]->hir(instructions, state);
} else {
func_name = id->primary_expression.identifier;
}
process_parameters(instructions, &actual_parameters, &this->expressions,
state);
@ -1778,6 +1884,10 @@ ast_function_expression::hir(exec_list *instructions,
match_function_by_name(func_name, &actual_parameters, state);
ir_rvalue *value = NULL;
if (sig == NULL) {
sig = match_subroutine_by_name(func_name, &actual_parameters, state, &sub_var);
}
if (sig == NULL) {
no_matching_function_error(func_name, &loc, &actual_parameters, state);
value = ir_rvalue::error_value(ctx);
@ -1785,7 +1895,7 @@ ast_function_expression::hir(exec_list *instructions,
/* an error has already been emitted */
value = ir_rvalue::error_value(ctx);
} else {
value = generate_call(instructions, sig, &actual_parameters, state);
value = generate_call(instructions, sig, &actual_parameters, sub_var, array_idx, state);
if (!value) {
ir_variable *const tmp = new(ctx) ir_variable(glsl_type::void_type,
"void_var",

View file

@ -54,6 +54,7 @@
#include "ast.h"
#include "glsl_types.h"
#include "program/hash_table.h"
#include "main/shaderobj.h"
#include "ir.h"
#include "ir_builder.h"
@ -2522,6 +2523,12 @@ apply_type_qualifier_to_variable(const struct ast_type_qualifier *qual,
}
}
if (qual->flags.q.subroutine && !qual->flags.q.uniform) {
_mesa_glsl_error(loc, state,
"`subroutine' may only be applied to uniforms, "
"subroutine type declarations, or function definitions");
}
if (qual->flags.q.constant || qual->flags.q.attribute
|| qual->flags.q.uniform
|| (qual->flags.q.varying && (state->stage == MESA_SHADER_FRAGMENT)))
@ -3597,7 +3604,7 @@ ast_declarator_list::hir(exec_list *instructions,
foreach_list_typed (ast_declaration, decl, link, &this->declarations) {
const struct glsl_type *var_type;
ir_variable *var;
const char *identifier = decl->identifier;
/* FINISHME: Emit a warning if a variable declaration shadows a
* FINISHME: declaration at a higher scope.
*/
@ -3615,10 +3622,24 @@ ast_declarator_list::hir(exec_list *instructions,
continue;
}
if (this->type->qualifier.flags.q.subroutine) {
const glsl_type *t;
const char *name;
t = state->symbols->get_type(this->type->specifier->type_name);
if (!t)
_mesa_glsl_error(& loc, state,
"invalid type in declaration of `%s'",
decl->identifier);
name = ralloc_asprintf(ctx, "%s_%s", _mesa_shader_stage_to_subroutine_prefix(state->stage), decl->identifier);
identifier = name;
}
var_type = process_array_type(&loc, decl_type, decl->array_specifier,
state);
var = new(ctx) ir_variable(var_type, decl->identifier, ir_var_auto);
var = new(ctx) ir_variable(var_type, identifier, ir_var_auto);
/* The 'varying in' and 'varying out' qualifiers can only be used with
* ARB_geometry_shader4 and EXT_geometry_shader4, which we don't support
@ -3690,6 +3711,8 @@ ast_declarator_list::hir(exec_list *instructions,
*/
if (this->type->qualifier.flags.q.attribute) {
mode = "attribute";
} else if (this->type->qualifier.flags.q.subroutine) {
mode = "subroutine uniform";
} else if (this->type->qualifier.flags.q.uniform) {
mode = "uniform";
} else if (this->type->qualifier.flags.q.varying) {
@ -3930,6 +3953,9 @@ ast_declarator_list::hir(exec_list *instructions,
if (state->stage == MESA_SHADER_TESS_CTRL) {
handle_tess_ctrl_shader_output_decl(state, loc, var);
}
} else if (var->type->contains_subroutine()) {
/* declare subroutine uniforms as hidden */
var->data.how_declared = ir_var_hidden;
}
/* Integer fragment inputs must be qualified with 'flat'. In GLSL ES,
@ -4394,6 +4420,7 @@ ast_function::hir(exec_list *instructions,
ir_function *f = NULL;
ir_function_signature *sig = NULL;
exec_list hir_parameters;
YYLTYPE loc = this->get_location();
const char *const name = identifier;
@ -4445,6 +4472,17 @@ ast_function::hir(exec_list *instructions,
return_type = glsl_type::error_type;
}
/* ARB_shader_subroutine states:
* "Subroutine declarations cannot be prototyped. It is an error to prepend
* subroutine(...) to a function declaration."
*/
if (this->return_type->qualifier.flags.q.subroutine_def && !is_definition) {
YYLTYPE loc = this->get_location();
_mesa_glsl_error(&loc, state,
"function declaration `%s' cannot have subroutine prepended",
name);
}
/* From page 56 (page 62 of the PDF) of the GLSL 1.30 spec:
* "No qualifier is allowed on the return type of a function."
*/
@ -4482,15 +4520,15 @@ ast_function::hir(exec_list *instructions,
f = state->symbols->get_function(name);
if (f == NULL) {
f = new(ctx) ir_function(name);
if (!state->symbols->add_function(f)) {
/* This function name shadows a non-function use of the same name. */
YYLTYPE loc = this->get_location();
_mesa_glsl_error(&loc, state, "function name `%s' conflicts with "
"non-function", name);
return NULL;
if (!this->return_type->qualifier.flags.q.subroutine) {
if (!state->symbols->add_function(f)) {
/* This function name shadows a non-function use of the same name. */
YYLTYPE loc = this->get_location();
_mesa_glsl_error(&loc, state, "function name `%s' conflicts with "
"non-function", name);
return NULL;
}
}
emit_function(state, f);
}
@ -4577,6 +4615,44 @@ ast_function::hir(exec_list *instructions,
sig->replace_parameters(&hir_parameters);
signature = sig;
if (this->return_type->qualifier.flags.q.subroutine_def) {
int idx;
f->num_subroutine_types = this->return_type->qualifier.subroutine_list->declarations.length();
f->subroutine_types = ralloc_array(state, const struct glsl_type *,
f->num_subroutine_types);
idx = 0;
foreach_list_typed(ast_declaration, decl, link, &this->return_type->qualifier.subroutine_list->declarations) {
const struct glsl_type *type;
/* the subroutine type must be already declared */
type = state->symbols->get_type(decl->identifier);
if (!type) {
_mesa_glsl_error(& loc, state, "unknown type '%s' in subroutine function definition", decl->identifier);
}
f->subroutine_types[idx++] = type;
}
state->subroutines = (ir_function **)reralloc(state, state->subroutines,
ir_function *,
state->num_subroutines + 1);
state->subroutines[state->num_subroutines] = f;
state->num_subroutines++;
}
if (this->return_type->qualifier.flags.q.subroutine) {
if (!state->symbols->add_type(this->identifier, glsl_type::get_subroutine_instance(this->identifier))) {
_mesa_glsl_error(& loc, state, "type '%s' previously defined", this->identifier);
return NULL;
}
state->subroutine_types = (ir_function **)reralloc(state, state->subroutine_types,
ir_function *,
state->num_subroutine_types + 1);
state->subroutine_types[state->num_subroutine_types] = f;
state->num_subroutine_types++;
f->is_subroutine = true;
}
/* Function declarations (prototypes) do not have r-values.
*/
return NULL;

View file

@ -40,7 +40,12 @@ ast_type_specifier::print(void) const
bool
ast_fully_specified_type::has_qualifiers() const
{
return this->qualifier.flags.i != 0;
/* 'subroutine' isnt a real qualifier. */
ast_type_qualifier subroutine_only;
subroutine_only.flags.i = 0;
subroutine_only.flags.q.subroutine = 1;
subroutine_only.flags.q.subroutine_def = 1;
return (this->qualifier.flags.i & ~subroutine_only.flags.i) != 0;
}
bool ast_type_qualifier::has_interpolation() const

View file

@ -595,6 +595,10 @@ subroutine KEYWORD_WITH_ALT(400, 300, 400, 0, yyextra->ARB_shader_subroutine_ena
return classify_identifier(state, yytext);
}
\. { struct _mesa_glsl_parse_state *state = yyextra;
state->is_field = true;
return DOT_TOK; }
. { return yytext[0]; }
%%
@ -602,6 +606,10 @@ subroutine KEYWORD_WITH_ALT(400, 300, 400, 0, yyextra->ARB_shader_subroutine_ena
int
classify_identifier(struct _mesa_glsl_parse_state *state, const char *name)
{
if (state->is_field) {
state->is_field = false;
return FIELD_SELECTION;
}
if (state->symbols->get_variable(name) || state->symbols->get_function(name))
return IDENTIFIER;
else if (state->symbols->get_type(name))

View file

@ -121,7 +121,7 @@ static bool match_layout_qualifier(const char *s1, const char *s2,
ast_case_statement *case_statement;
ast_case_statement_list *case_statement_list;
ast_interface_block *interface_block;
ast_subroutine_list *subroutine_list;
struct {
ast_node *cond;
ast_expression *rest;
@ -186,7 +186,7 @@ static bool match_layout_qualifier(const char *s1, const char *s2,
%token PRAGMA_OPTIMIZE_ON PRAGMA_OPTIMIZE_OFF
%token PRAGMA_INVARIANT_ALL
%token LAYOUT_TOK
%token DOT_TOK
/* Reserved words that are not actually used in the grammar.
*/
%token ASM CLASS UNION ENUM TYPEDEF TEMPLATE THIS PACKED_TOK GOTO
@ -215,6 +215,8 @@ static bool match_layout_qualifier(const char *s1, const char *s2,
%type <type_qualifier> layout_qualifier_id_list layout_qualifier_id
%type <type_qualifier> interface_block_layout_qualifier
%type <type_qualifier> memory_qualifier
%type <type_qualifier> subroutine_qualifier
%type <subroutine_list> subroutine_type_list
%type <type_qualifier> interface_qualifier
%type <type_specifier> type_specifier
%type <type_specifier> type_specifier_nonarray
@ -260,10 +262,6 @@ static bool match_layout_qualifier(const char *s1, const char *s2,
%type <expression> function_call_generic
%type <expression> function_call_or_method
%type <expression> function_call
%type <expression> method_call_generic
%type <expression> method_call_header_with_parameters
%type <expression> method_call_header_no_parameters
%type <expression> method_call_header
%type <n> assignment_operator
%type <n> unary_operator
%type <expression> function_identifier
@ -476,7 +474,7 @@ postfix_expression:
{
$$ = $1;
}
| postfix_expression '.' any_identifier
| postfix_expression DOT_TOK FIELD_SELECTION
{
void *ctx = state;
$$ = new(ctx) ast_expression(ast_field_selection, $1, NULL, NULL);
@ -507,12 +505,6 @@ function_call:
function_call_or_method:
function_call_generic
| postfix_expression '.' method_call_generic
{
void *ctx = state;
$$ = new(ctx) ast_expression(ast_field_selection, $1, $3, NULL);
$$->set_location_range(@1, @3);
}
;
function_call_generic:
@ -554,62 +546,17 @@ function_identifier:
$$ = new(ctx) ast_function_expression($1);
$$->set_location(@1);
}
| variable_identifier
| postfix_expression
{
void *ctx = state;
ast_expression *callee = new(ctx) ast_expression($1);
callee->set_location(@1);
$$ = new(ctx) ast_function_expression(callee);
$$ = new(ctx) ast_function_expression($1);
$$->set_location(@1);
}
| FIELD_SELECTION
{
void *ctx = state;
ast_expression *callee = new(ctx) ast_expression($1);
callee->set_location(@1);
$$ = new(ctx) ast_function_expression(callee);
$$->set_location(@1);
}
;
method_call_generic:
method_call_header_with_parameters ')'
| method_call_header_no_parameters ')'
;
method_call_header_no_parameters:
method_call_header VOID_TOK
| method_call_header
;
method_call_header_with_parameters:
method_call_header assignment_expression
{
$$ = $1;
$$->set_location(@1);
$$->expressions.push_tail(& $2->link);
}
| method_call_header_with_parameters ',' assignment_expression
{
$$ = $1;
$$->set_location(@1);
$$->expressions.push_tail(& $3->link);
}
;
// Grammar Note: Constructors look like methods, but lexical
// analysis recognized most of them as keywords. They are now
// recognized through "type_specifier".
method_call_header:
variable_identifier '('
{
void *ctx = state;
ast_expression *callee = new(ctx) ast_expression($1);
callee->set_location(@1);
$$ = new(ctx) ast_function_expression(callee);
$$->set_location(@1);
}
;
// Grammar Note: No traditional style type casts.
unary_expression:
@ -910,7 +857,11 @@ function_header:
$$->return_type = $1;
$$->identifier = $2;
state->symbols->add_function(new(state) ir_function($2));
if ($1->qualifier.flags.q.subroutine) {
/* add type for IDENTIFIER search */
state->symbols->add_type($2, glsl_type::get_subroutine_instance($2));
} else
state->symbols->add_function(new(state) ir_function($2));
state->symbols->push_scope();
}
;
@ -1676,6 +1627,41 @@ interface_block_layout_qualifier:
}
;
subroutine_qualifier:
SUBROUTINE
{
memset(& $$, 0, sizeof($$));
$$.flags.q.subroutine = 1;
}
| SUBROUTINE '(' subroutine_type_list ')'
{
memset(& $$, 0, sizeof($$));
$$.flags.q.subroutine_def = 1;
$$.subroutine_list = $3;
}
;
subroutine_type_list:
any_identifier
{
void *ctx = state;
ast_declaration *decl = new(ctx) ast_declaration($1, NULL, NULL);
decl->set_location(@1);
$$ = new(ctx) ast_subroutine_list();
$$->declarations.push_tail(&decl->link);
}
| subroutine_type_list ',' any_identifier
{
void *ctx = state;
ast_declaration *decl = new(ctx) ast_declaration($3, NULL, NULL);
decl->set_location(@3);
$$ = $1;
$$->declarations.push_tail(&decl->link);
}
;
interpolation_qualifier:
SMOOTH
{
@ -1711,6 +1697,7 @@ type_qualifier:
| interpolation_qualifier
| layout_qualifier
| memory_qualifier
| subroutine_qualifier
| precision_qualifier
{
memset(&$$, 0, sizeof($$));
@ -1801,6 +1788,11 @@ type_qualifier:
$$ = $1;
$$.merge_qualifier(&@1, state, $2);
}
| subroutine_qualifier type_qualifier
{
$$ = $1;
$$.merge_qualifier(&@1, state, $2);
}
| auxiliary_storage_qualifier type_qualifier
{
if ($2.has_auxiliary_storage()) {

View file

@ -173,6 +173,10 @@ _mesa_glsl_parse_state::_mesa_glsl_parse_state(struct gl_context *_ctx,
this->all_invariant = false;
this->user_structures = NULL;
this->num_user_structures = 0;
this->num_subroutines = 0;
this->subroutines = NULL;
this->num_subroutine_types = 0;
this->subroutine_types = NULL;
/* supported_versions should be large enough to support the known desktop
* GLSL versions plus 3 GLES versions (ES 1.00, ES 3.00, and ES 3.10))
@ -855,6 +859,15 @@ _mesa_ast_set_aggregate_type(const glsl_type *type,
void
_mesa_ast_type_qualifier_print(const struct ast_type_qualifier *q)
{
if (q->flags.q.subroutine)
printf("subroutine ");
if (q->flags.q.subroutine_def) {
printf("subroutine (");
q->subroutine_list->print();
printf(")");
}
if (q->flags.q.constant)
printf("const ");
@ -1447,6 +1460,15 @@ ast_struct_specifier::ast_struct_specifier(const char *identifier,
is_declaration = true;
}
void ast_subroutine_list::print(void) const
{
foreach_list_typed (ast_node, ast, link, & this->declarations) {
if (&ast->link != this->declarations.get_head())
printf(", ");
ast->print();
}
}
static void
set_shader_inout_layout(struct gl_shader *shader,
struct _mesa_glsl_parse_state *state)

View file

@ -589,6 +589,25 @@ struct _mesa_glsl_parse_state {
unsigned atomic_counter_offsets[MAX_COMBINED_ATOMIC_BUFFERS];
bool allow_extension_directive_midshader;
/**
* Known subroutine type declarations.
*/
int num_subroutine_types;
ir_function **subroutine_types;
/**
* Functions that are associated with
* subroutine types.
*/
int num_subroutines;
ir_function **subroutines;
/**
* field selection temporary parser storage -
* did the parser just parse a dot.
*/
bool is_field;
};
# define YYLLOC_DEFAULT(Current, Rhs, N) \

View file

@ -56,45 +56,6 @@ _mesa_ast_field_selection_to_hir(const ast_expression *expr,
"structure",
expr->primary_expression.identifier);
}
} else if (expr->subexpressions[1] != NULL) {
/* Handle "method calls" in GLSL 1.20 - namely, array.length() */
state->check_version(120, 300, &loc, "methods not supported");
ast_expression *call = expr->subexpressions[1];
assert(call->oper == ast_function_call);
const char *method;
method = call->subexpressions[0]->primary_expression.identifier;
if (strcmp(method, "length") == 0) {
if (!call->expressions.is_empty())
_mesa_glsl_error(&loc, state, "length method takes no arguments");
if (op->type->is_array()) {
if (op->type->is_unsized_array())
_mesa_glsl_error(&loc, state, "length called on unsized array");
result = new(ctx) ir_constant(op->type->array_size());
} else if (op->type->is_vector()) {
if (state->ARB_shading_language_420pack_enable) {
/* .length() returns int. */
result = new(ctx) ir_constant((int) op->type->vector_elements);
} else {
_mesa_glsl_error(&loc, state, "length method on matrix only available"
"with ARB_shading_language_420pack");
}
} else if (op->type->is_matrix()) {
if (state->ARB_shading_language_420pack_enable) {
/* .length() returns int. */
result = new(ctx) ir_constant((int) op->type->matrix_columns);
} else {
_mesa_glsl_error(&loc, state, "length method on matrix only available"
"with ARB_shading_language_420pack");
}
}
} else {
_mesa_glsl_error(&loc, state, "unknown method: `%s'", method);
}
} else if (op->type->is_vector() ||
(state->ARB_shading_language_420pack_enable &&
op->type->is_scalar())) {