diff --git a/ocelot/include/ocelot/parser/PTXParser.h b/ocelot/include/ocelot/parser/PTXParser.h index 1527b55c..a017de5d 100644 --- a/ocelot/include/ocelot/parser/PTXParser.h +++ b/ocelot/include/ocelot/parser/PTXParser.h @@ -150,7 +150,7 @@ namespace parser void maxnreg( unsigned int regs ); void maxntid( unsigned int tidx, unsigned int tidy = 1024, unsigned int tidz = 1024 ); - void ctapersm( int target, unsigned int ctas ); + void ctapersm( const char* target, unsigned int ctas ); void maxnctapersm( unsigned int ctas ); void maxnctapersm(); void minnctapersm( unsigned int ctas ); @@ -171,6 +171,7 @@ namespace parser void singleList( float value ); void singleList1( float value ); void targetElement( int token ); + void targetElement( const char* target ); void target(); void noAddressSpace(); void addressSpace( int token ); diff --git a/ocelot/src/executive/CooperativeThreadArray.cpp b/ocelot/src/executive/CooperativeThreadArray.cpp index 81acd6dd..fd264bbb 100644 --- a/ocelot/src/executive/CooperativeThreadArray.cpp +++ b/ocelot/src/executive/CooperativeThreadArray.cpp @@ -859,6 +859,52 @@ ir::PTXS64 executive::CooperativeThreadArray::getRegAsS64(int threadID, \param threadID ID of the active thread \reg register index */ +/*! + PTX keeps .f16 values as an IEEE754 binary16 bit pattern in the low half of the + register slot. f32 represents every f16 exactly, so widening is lossless and a + cvt from f16 is exactly the cvt from the widened f32. +*/ +static ir::PTXF32 halfToFloat(ir::PTXU16 h) { + ir::PTXU32 sign = (ir::PTXU32)(h & 0x8000) << 16; + ir::PTXU32 exp = (h >> 10) & 0x1f, mant = h & 0x3ff, bits; + if (exp == 0) { + if (mant == 0) bits = sign; + else { + exp = 127 - 15 + 1; + while ((mant & 0x400) == 0) { mant <<= 1; --exp; } + bits = sign | (exp << 23) | ((mant & 0x3ff) << 13); + } + } + else if (exp == 0x1f) bits = sign | 0x7f800000 | (mant << 13); + else bits = sign | ((exp - 15 + 127) << 23) | (mant << 13); + return hydrazine::bit_cast(bits); +} + +/*! binary32 to binary16, round to nearest even, which is the PTX cvt.rn default. */ +static ir::PTXU16 floatToHalf(ir::PTXF32 f) { + ir::PTXU32 bits = hydrazine::bit_cast(f); + ir::PTXU16 sign = (ir::PTXU16)((bits >> 16) & 0x8000); + ir::PTXU32 rawexp = (bits >> 23) & 0xff, mant = bits & 0x7fffff; + if (rawexp == 0xff) { + return sign | 0x7c00 | (mant ? (ir::PTXU16)((mant >> 13) | 0x200) : 0); + } + int exp = (int)rawexp - 127 + 15; + if (exp >= 0x1f) return sign | 0x7c00; + if (exp <= 0) { + if (exp < -10) return sign; + mant |= 0x800000; + int shift = 14 - exp; + ir::PTXU32 h = mant >> shift; + ir::PTXU32 rem = mant & ((1u << shift) - 1), half = 1u << (shift - 1); + if (rem > half || (rem == half && (h & 1))) ++h; + return sign | (ir::PTXU16)h; + } + ir::PTXU16 h = (ir::PTXU16)((exp << 10) | (mant >> 13)); + ir::PTXU32 rem = mant & 0x1fff; + if (rem > 0x1000 || (rem == 0x1000 && (h & 1))) ++h; + return sign | h; +} + ir::PTXF32 executive::CooperativeThreadArray::getRegAsF32(int threadID, ir::PTXOperand::RegisterType reg) { ir::PTXF32 r = *( (ir::PTXF32*)( @@ -1500,6 +1546,9 @@ ir::PTXF32 executive::CooperativeThreadArray::operandAsF32(int threadID, const ir::PTXOperand &op) { switch (op.addressMode) { case ir::PTXOperand::Register: + if (op.type == ir::PTXOperand::f16) { + return halfToFloat(getRegAsB16(threadID, op.reg)); + } return getRegAsF32(threadID, op.reg); case ir::PTXOperand::Immediate: return (ir::PTXF32)(op.imm_single); @@ -1717,7 +1766,16 @@ void executive::CooperativeThreadArray::eval_Abs(CTAContext &context, void executive::CooperativeThreadArray::eval_Add(CTAContext &context, const ir::PTXInstruction &instr) { trace(); - if (instr.type == ir::PTXOperand::f32) { + if (instr.type == ir::PTXOperand::f16) { + for (int threadID = 0; threadID < threadCount; threadID++) { + if (!context.predicated(threadID, instr)) continue; + ir::PTXF32 a = operandAsF32(threadID, instr.a), + b = operandAsF32(threadID, instr.b); + setRegAsB16(threadID, instr.d.reg, + floatToHalf(sat(instr.modifier, a + b))); + } + } + else if (instr.type == ir::PTXOperand::f32) { for (int threadID = 0; threadID < threadCount; threadID++) { if (!context.predicated(threadID, instr)) continue; ir::PTXF32 d, a = ftz(instr.modifier, operandAsF32(threadID, instr.a)), @@ -3009,6 +3067,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsS64(threadID, instr.d.reg, d); } break; + case ir::PTXOperand::f16: + { + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(operandAsB8(threadID, instr.a), + instr.modifier))); + } + break; case ir::PTXOperand::f32: { setRegAsF32(threadID, instr.d.reg, @@ -3059,6 +3124,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsU64(threadID, instr.d.reg, a); } break; + case ir::PTXOperand::f16: + { + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(operandAsS8(threadID, instr.a), + instr.modifier))); + } + break; case ir::PTXOperand::f32: { setRegAsF32(threadID, instr.d.reg, @@ -3126,6 +3198,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsS64(threadID, instr.d.reg, d); } break; + case ir::PTXOperand::f16: + { + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(operandAsB16(threadID, instr.a), + instr.modifier))); + } + break; case ir::PTXOperand::f32: { setRegAsF32(threadID, instr.d.reg, @@ -3192,6 +3271,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsU64(threadID, instr.d.reg, a); } break; + case ir::PTXOperand::f16: + { + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(operandAsS16(threadID, instr.a), + instr.modifier))); + } + break; case ir::PTXOperand::f32: { setRegAsF32(threadID, instr.d.reg, @@ -3274,6 +3360,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsS64(threadID, instr.d.reg, d); } break; + case ir::PTXOperand::f16: + { + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(operandAsU32(threadID, instr.a), + instr.modifier))); + } + break; case ir::PTXOperand::f32: { setRegAsF32(threadID, instr.d.reg, @@ -3354,6 +3447,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsS64(threadID, instr.d.reg, a); } break; + case ir::PTXOperand::f16: + { + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(operandAsS32(threadID, instr.a), + instr.modifier))); + } + break; case ir::PTXOperand::f32: { setRegAsF32(threadID, instr.d.reg, @@ -3449,6 +3549,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsS64(threadID, instr.d.reg, a); } break; + case ir::PTXOperand::f16: + { + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(operandAsS64(threadID, instr.a), + instr.modifier))); + } + break; case ir::PTXOperand::f32: { setRegAsF32(threadID, instr.d.reg, @@ -3546,6 +3653,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsS64(threadID, instr.d.reg, d); } break; + case ir::PTXOperand::f16: + { + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(operandAsU64(threadID, instr.a), + instr.modifier))); + } + break; case ir::PTXOperand::f32: { setRegAsF32(threadID, instr.d.reg, @@ -3567,6 +3681,7 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, } } break; + case ir::PTXOperand::f16: // fall through, widened by operandAsF32 case ir::PTXOperand::f32: { switch (instr.type) { @@ -3727,6 +3842,17 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsS64(threadID, instr.d.reg, d); } break; + case ir::PTXOperand::f16: + { + ir::PTXF32 a = operandAsF32(threadID, instr.a); + + a = roundToInt(a, instr.modifier, context, + instr); + + setRegAsB16(threadID, instr.d.reg, + floatToHalf(sat(instr.modifier, a))); + } + break; case ir::PTXOperand::f32: { ir::PTXF32 a = operandAsF32(threadID, instr.a); @@ -3914,6 +4040,13 @@ void executive::CooperativeThreadArray::eval_Cvt(CTAContext &context, setRegAsS64(threadID, instr.d.reg, d); } break; + case ir::PTXOperand::f16: + { + ir::PTXF64 a = operandAsF64(threadID, instr.a); + setRegAsB16(threadID, instr.d.reg, + floatToHalf(toF32(a, instr.modifier))); + } + break; case ir::PTXOperand::f32: { ir::PTXF64 a = operandAsF64(threadID, instr.a); @@ -4373,7 +4506,15 @@ void executive::CooperativeThreadArray::eval_Div(CTAContext &context, void executive::CooperativeThreadArray::eval_Ex2(CTAContext &context, const ir::PTXInstruction &instr) { trace(); - if (instr.type == ir::PTXOperand::f32) { + if (instr.type == ir::PTXOperand::f16) { + for (int threadID = 0; threadID < threadCount; threadID++) { + if (!context.predicated(threadID, instr)) continue; + ir::PTXF32 a = operandAsF32(threadID, instr.a); + setRegAsB16(threadID, instr.d.reg, + floatToHalf(hydrazine::exp2f(a))); + } + } + else if (instr.type == ir::PTXOperand::f32) { for (int threadID = 0; threadID < threadCount; threadID++) { if (!context.predicated(threadID, instr)) continue; @@ -5316,7 +5457,19 @@ void executive::CooperativeThreadArray::eval_Mad(CTAContext &context, void executive::CooperativeThreadArray::eval_Max(CTAContext &context, const ir::PTXInstruction &instr) { trace(); - if (instr.type == ir::PTXOperand::f32) { + if (instr.type == ir::PTXOperand::f16) { + for (int threadID = 0; threadID < threadCount; threadID++) { + if (!context.predicated(threadID, instr)) continue; + ir::PTXF32 a = operandAsF32(threadID, instr.a), + b = operandAsF32(threadID, instr.b); + ir::PTXF32 d; + if (hydrazine::isnan(a)) d = b; + else if (hydrazine::isnan(b)) d = a; + else d = (a > b) ? a : b; + setRegAsB16(threadID, instr.d.reg, floatToHalf(d)); + } + } + else if (instr.type == ir::PTXOperand::f32) { for (int threadID = 0; threadID < threadCount; threadID++) { if (!context.predicated(threadID, instr)) continue; @@ -5808,6 +5961,7 @@ void executive::CooperativeThreadArray::eval_Mov_imm(CTAContext &context, case ir::PTXOperand::u16: case ir::PTXOperand::s16: case ir::PTXOperand::b16: + case ir::PTXOperand::f16: // PTX materializes a half constant as mov.b16 into an .f16 reg { ir::PTXU16 a = operandAsU16(threadID, instr.a); setRegAsU16(threadID, instr.d.reg, a); @@ -5950,7 +6104,16 @@ void executive::CooperativeThreadArray::eval_Mul24(CTAContext &context, const ir */ void executive::CooperativeThreadArray::eval_Mul(CTAContext &context, const ir::PTXInstruction &instr) { trace(); - if (instr.type == ir::PTXOperand::f32) { + if (instr.type == ir::PTXOperand::f16) { + for (int threadID = 0; threadID < threadCount; threadID++) { + if (!context.predicated(threadID, instr)) continue; + ir::PTXF32 a = operandAsF32(threadID, instr.a), + b = operandAsF32(threadID, instr.b); + setRegAsB16(threadID, instr.d.reg, + floatToHalf(sat(instr.modifier, a * b))); + } + } + else if (instr.type == ir::PTXOperand::f32) { for (int threadID = 0; threadID < threadCount; threadID++) { if (!context.predicated(threadID, instr)) continue; @@ -7132,6 +7295,7 @@ void executive::CooperativeThreadArray::eval_SetP(CTAContext &context, } break; + case ir::PTXOperand::f16: // fall through, widened by operandAsF32 // single-precision float case ir::PTXOperand::f32: { diff --git a/ocelot/src/ir/PTXInstruction.cpp b/ocelot/src/ir/PTXInstruction.cpp index 099a0493..018f3fcc 100644 --- a/ocelot/src/ir/PTXInstruction.cpp +++ b/ocelot/src/ir/PTXInstruction.cpp @@ -504,8 +504,7 @@ std::string ir::PTXInstruction::valid() const { } case Add: { if ( !( type != PTXOperand::s8 && type != PTXOperand::u8 && - type != PTXOperand::b8 && type != PTXOperand::f16 - && type != PTXOperand::pred ) ) { + type != PTXOperand::b8 && type != PTXOperand::pred ) ) { return "invalid instruction type " + PTXOperand::toString( type ); } @@ -667,12 +666,14 @@ std::string ir::PTXInstruction::valid() const { return "operand 2 type " + PTXOperand::toString( a.type ) + " cannot be assigned to " + PTXOperand::toString( type ); } - if( !PTXOperand::valid( PTXOperand::u32, b.type ) ) { + if( !PTXOperand::valid( PTXOperand::u32, b.type ) + && b.addressMode != PTXOperand::Immediate ) { return "operand 3 type " + PTXOperand::toString( b.type ) + " cannot be assigned to " + PTXOperand::toString( PTXOperand::u32 ); } - if( !PTXOperand::valid( PTXOperand::u32, b.type ) ) { + if( !PTXOperand::valid( PTXOperand::u32, c.type ) + && c.addressMode != PTXOperand::Immediate ) { return "operand 4 type " + PTXOperand::toString( c.type ) + " cannot be assigned to " + PTXOperand::toString( PTXOperand::u32 ); @@ -921,7 +922,7 @@ std::string ir::PTXInstruction::valid() const { break; } case Ex2: { - if( !( type == PTXOperand::f32 ) ) { + if( !( type == PTXOperand::f32 || type == PTXOperand::f16 ) ) { return "invalid instruction type " + PTXOperand::toString( type ); } @@ -1122,8 +1123,7 @@ std::string ir::PTXInstruction::valid() const { } case Max: { if( !( type != PTXOperand::s8 && type != PTXOperand::u8 && - type != PTXOperand::b8 && type != PTXOperand::f16 - && type != PTXOperand::pred ) ) { + type != PTXOperand::b8 && type != PTXOperand::pred ) ) { return "invalid instruction type " + PTXOperand::toString( type ); } @@ -1178,14 +1178,8 @@ std::string ir::PTXInstruction::valid() const { break; } case Mov: { - if ( ( a.type == PTXOperand::f16 ) && - a.addressMode != PTXOperand::Address && - a.addressMode != PTXOperand::Immediate ) { - return "invalid type for operand A " - + PTXOperand::toString( a.type ); - } if ( !( d.type != PTXOperand::s8 && d.type != PTXOperand::u8 - && d.type != PTXOperand::b8 && d.type != PTXOperand::f16 ) ) { + && d.type != PTXOperand::b8 ) ) { return "invalid type for operand D " + PTXOperand::toString( d.type ); } @@ -1222,8 +1216,7 @@ std::string ir::PTXInstruction::valid() const { } case Mul: { if( type == PTXOperand::s8 || type == PTXOperand::u8 - || type == PTXOperand::b8 || type == PTXOperand::f16 - || type == PTXOperand::pred ) { + || type == PTXOperand::b8 || type == PTXOperand::pred ) { return "invalid instruction type " + PTXOperand::toString( type ); } @@ -1614,8 +1607,8 @@ std::string ir::PTXInstruction::valid() const { && type != PTXOperand::s64 && type != PTXOperand::u16 && type != PTXOperand::u32 && type != PTXOperand::u64 && type != PTXOperand::b16 && type != PTXOperand::b32 - && type != PTXOperand::b64 && type != PTXOperand::f32 - && type != PTXOperand::f64 ) { + && type != PTXOperand::b64 && type != PTXOperand::f16 + && type != PTXOperand::f32 && type != PTXOperand::f64 ) { return "invalid instruction type " + PTXOperand::toString( type ); } @@ -1862,8 +1855,7 @@ std::string ir::PTXInstruction::valid() const { } case Sub: { if ( !( type != PTXOperand::s8 && type != PTXOperand::u8 && - type != PTXOperand::b8 && type != PTXOperand::f16 - && type != PTXOperand::pred ) ) { + type != PTXOperand::b8 && type != PTXOperand::pred ) ) { return "invalid instruction type " + PTXOperand::toString( type ); } diff --git a/ocelot/src/parser/PTXLexer.cpp b/ocelot/src/parser/PTXLexer.cpp index 8d9c3c01..f1871cae 100644 --- a/ocelot/src/parser/PTXLexer.cpp +++ b/ocelot/src/parser/PTXLexer.cpp @@ -134,15 +134,8 @@ namespace parser CASE(TOKEN_MAXNREG) CASE(TOKEN_MAXNTID) CASE(TOKEN_MAXNCTAPERSM) - CASE(TOKEN_SM10) + CASE(TOKEN_SHADER_MODEL) CASE(TOKEN_MINNCTAPERSM) - CASE(TOKEN_SM11) - CASE(TOKEN_SM12) - CASE(TOKEN_SM13) - CASE(TOKEN_SM20) - CASE(TOKEN_SM21) - CASE(TOKEN_SM30) - CASE(TOKEN_SM35) CASE(TOKEN_MAP_F64_TO_F32) CASE(TOKEN_CONST) CASE(TOKEN_GLOBAL) diff --git a/ocelot/src/parser/PTXParser.cpp b/ocelot/src/parser/PTXParser.cpp index 636237be..0b369b70 100644 --- a/ocelot/src/parser/PTXParser.cpp +++ b/ocelot/src/parser/PTXParser.cpp @@ -376,7 +376,7 @@ namespace parser } - void PTXParser::State::ctapersm( int target, unsigned int ctas ) + void PTXParser::State::ctapersm( const char* target, unsigned int ctas ) { report( " Rule: shareModel ':' TOKEN_DECIMAL_CONSTANT" ); } @@ -598,15 +598,7 @@ namespace parser void PTXParser::State::targetElement( int token ) { report( " Rule: targetOption" ); - if( token == TOKEN_SM10 ) statement.targets.push_back( "sm_10" ); - else if( token == TOKEN_SM11 ) statement.targets.push_back( "sm_11" ); - else if( token == TOKEN_SM12 ) statement.targets.push_back( "sm_12" ); - else if( token == TOKEN_SM13 ) statement.targets.push_back( "sm_13" ); - else if( token == TOKEN_SM20 ) statement.targets.push_back( "sm_20" ); - else if( token == TOKEN_SM21 ) statement.targets.push_back( "sm_21" ); - else if( token == TOKEN_SM30 ) statement.targets.push_back( "sm_30" ); - else if( token == TOKEN_SM35 ) statement.targets.push_back( "sm_35" ); - else if( token == TOKEN_MAP_F64_TO_F32 ) + if( token == TOKEN_MAP_F64_TO_F32 ) { statement.targets.push_back( "map_f64_to_f32" ); } @@ -622,6 +614,12 @@ namespace parser } } + void PTXParser::State::targetElement( const char* target ) + { + report( " Rule: targetOption" ); + statement.targets.push_back( target ); + } + void PTXParser::State::target() { report( " Rule: TARGET targetElementList" ); diff --git a/ocelot/src/parser/ptx.ll b/ocelot/src/parser/ptx.ll index e3d747ef..927f1ccb 100644 --- a/ocelot/src/parser/ptx.ll +++ b/ocelot/src/parser/ptx.ll @@ -317,22 +317,7 @@ LABEL ({IDENTIFIER}{WHITESPACE}":") ".gl" { yylval->value = TOKEN_GL; return TOKEN_GL; } ".sys" { yylval->value = TOKEN_SYS; return TOKEN_SYS; } -"sm_10" { yylval->value = TOKEN_SM10; - return TOKEN_SM10; } -"sm_11" { yylval->value = TOKEN_SM11; - return TOKEN_SM11; } -"sm_12" { yylval->value = TOKEN_SM12; - return TOKEN_SM12; } -"sm_13" { yylval->value = TOKEN_SM13; - return TOKEN_SM13; } -"sm_20" { yylval->value = TOKEN_SM20; - return TOKEN_SM20; } -"sm_21" { yylval->value = TOKEN_SM21; - return TOKEN_SM21; } -"sm_30" { yylval->value = TOKEN_SM30; - return TOKEN_SM30; } -"sm_35" { yylval->value = TOKEN_SM35; - return TOKEN_SM35; } +"sm_"[0-9]+[a-zA-Z]* { sstrcpy( yylval->text, yytext, 1024 ); return TOKEN_SHADER_MODEL; } "map_f64_to_f32" { yylval->value = TOKEN_MAP_F64_TO_F32; return TOKEN_MAP_F64_TO_F32; } "texmode_independent" { yylval->value = TOKEN_TEXMODE_INDEPENDENT; diff --git a/ocelot/src/parser/ptxgrammar.yy b/ocelot/src/parser/ptxgrammar.yy index 32ab8e38..a5d04981 100644 --- a/ocelot/src/parser/ptxgrammar.yy +++ b/ocelot/src/parser/ptxgrammar.yy @@ -76,8 +76,8 @@ %token TOKEN_SECTION TOKEN_ADDRESS_SIZE TOKEN_WEAK %token TOKEN_MAXNREG TOKEN_MAXNTID TOKEN_MAXNCTAPERSM TOKEN_MINNCTAPERSM -%token TOKEN_SM11 TOKEN_SM12 TOKEN_SM13 TOKEN_SM20 TOKEN_MAP_F64_TO_F32 -%token TOKEN_SM21 TOKEN_SM10 TOKEN_SM30 TOKEN_SM35 +%token TOKEN_MAP_F64_TO_F32 +%token TOKEN_SHADER_MODEL %token TOKEN_TEXMODE_INDEPENDENT TOKEN_TEXMODE_UNIFIED %token TOKEN_CONST TOKEN_GLOBAL TOKEN_LOCAL TOKEN_PARAM TOKEN_PRAGMA TOKEN_PTR @@ -259,16 +259,17 @@ singleList : '{' singleListSingle '}' ',' '{' singleListSingle '}'; singleInitializer : singleList | '{' singleList '}' | '{' singleListSingle '}' | singleListSingle; -shaderModel : TOKEN_SM10 | TOKEN_SM11 | TOKEN_SM12 | TOKEN_SM13 | TOKEN_SM20 - | TOKEN_SM21 | TOKEN_SM30 | TOKEN_SM35; - floatingPointOption : TOKEN_MAP_F64_TO_F32; textureOption: TOKEN_TEXMODE_INDEPENDENT | TOKEN_TEXMODE_UNIFIED; -targetOption : shaderModel | floatingPointOption | textureOption; +targetOption : floatingPointOption | textureOption; targetElement : targetOption { state.targetElement( $1 ); +} + | TOKEN_SHADER_MODEL +{ + state.targetElement( $1 ); }; targetElementList : /* empty string */ | targetElement @@ -603,9 +604,9 @@ maxntid : TOKEN_MAXNTID TOKEN_DECIMAL_CONSTANT ',' TOKEN_DECIMAL_CONSTANT ',' state.maxntid( $2, $4, $6 ); }; -ctapersm : shaderModel ':' TOKEN_DECIMAL_CONSTANT +ctapersm : TOKEN_SHADER_MODEL ':' TOKEN_DECIMAL_CONSTANT { - state.ctapersm( $1, $3 ); + state.ctapersm( $1, $3 ); }; ctapersmList : ctapersm | ctapersmList ',' ctapersm;