diff options
Diffstat (limited to 'Externals/mojoshader/1067/mojoshader_assembler.c')
| -rw-r--r-- | Externals/mojoshader/1067/mojoshader_assembler.c | 1766 |
1 files changed, 1766 insertions, 0 deletions
diff --git a/Externals/mojoshader/1067/mojoshader_assembler.c b/Externals/mojoshader/1067/mojoshader_assembler.c new file mode 100644 index 00000000..7885bd45 --- /dev/null +++ b/Externals/mojoshader/1067/mojoshader_assembler.c @@ -0,0 +1,1766 @@ +/** + * MojoShader; generate shader programs from bytecode of compiled + * Direct3D shaders. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +// !!! FIXME: this should probably use a formal grammar and not a hand-written +// !!! FIXME: pile of C code. + +#define __MOJOSHADER_INTERNAL__ 1 +#include "mojoshader_internal.h" + +#if !SUPPORT_PROFILE_BYTECODE +#error Shader assembler needs bytecode profile. Fix your build. +#endif + +#if DEBUG_ASSEMBLER_PARSER + #define print_debug_token(token, len, val) \ + MOJOSHADER_print_debug_token("ASSEMBLER", token, len, val) +#else + #define print_debug_token(token, len, val) +#endif + + +typedef struct SourcePos +{ + const char *filename; + uint32 line; +} SourcePos; + + +// Context...this is state that changes as we assemble a shader... +typedef struct Context +{ + int isfail; + int out_of_memory; + MOJOSHADER_malloc malloc; + MOJOSHADER_free free; + void *malloc_data; + const char *current_file; + int current_position; + ErrorList *errors; + Preprocessor *preprocessor; + MOJOSHADER_shaderType shader_type; + uint8 major_ver; + uint8 minor_ver; + int pushedback; + const char *token; // assembler token! + unsigned int tokenlen; // assembler token! + Token tokenval; // assembler token! + uint32 version_token; // bytecode token! + uint32 tokenbuf[16]; // bytecode tokens! + int tokenbufpos; // bytecode tokens! + DestArgInfo dest_arg; + Buffer *output; + Buffer *token_to_source; + Buffer *ctab; +} Context; + + +// !!! FIXME: cut and paste between every damned source file follows... +// !!! FIXME: We need to make some sort of ContextBase that applies to all +// !!! FIXME: files and move this stuff to mojoshader_common.c ... + +// Convenience functions for allocators... + +static inline void out_of_memory(Context *ctx) +{ + ctx->isfail = ctx->out_of_memory = 1; +} // out_of_memory + +static inline void *Malloc(Context *ctx, const size_t len) +{ + void *retval = ctx->malloc((int) len, ctx->malloc_data); + if (retval == NULL) + out_of_memory(ctx); + return retval; +} // Malloc + +static inline char *StrDup(Context *ctx, const char *str) +{ + char *retval = (char *) Malloc(ctx, strlen(str) + 1); + if (retval != NULL) + strcpy(retval, str); + return retval; +} // StrDup + +static inline void Free(Context *ctx, void *ptr) +{ + ctx->free(ptr, ctx->malloc_data); +} // Free + +static void *MallocBridge(int bytes, void *data) +{ + return Malloc((Context *) data, (size_t) bytes); +} // MallocBridge + +static void FreeBridge(void *ptr, void *data) +{ + Free((Context *) data, ptr); +} // FreeBridge + + +static void failf(Context *ctx, const char *fmt, ...) ISPRINTF(2,3); +static void failf(Context *ctx, const char *fmt, ...) +{ + ctx->isfail = 1; + if (ctx->out_of_memory) + return; + + va_list ap; + va_start(ap, fmt); + errorlist_add_va(ctx->errors, ctx->current_file, ctx->current_position, fmt, ap); + va_end(ap); +} // failf + +static inline void fail(Context *ctx, const char *reason) +{ + failf(ctx, "%s", reason); +} // fail + +static inline int isfail(const Context *ctx) +{ + return ctx->isfail; +} // isfail + + +// Shader model version magic... + +static inline uint32 ver_ui32(const uint8 major, const uint8 minor) +{ + return ( (((uint32) major) << 16) | (((minor) == 0xFF) ? 0 : (minor)) ); +} // version_ui32 + +static inline int shader_version_atleast(const Context *ctx, const uint8 maj, + const uint8 min) +{ + return (ver_ui32(ctx->major_ver, ctx->minor_ver) >= ver_ui32(maj, min)); +} // shader_version_atleast + +static inline int shader_is_pixel(const Context *ctx) +{ + return (ctx->shader_type == MOJOSHADER_TYPE_PIXEL); +} // shader_is_pixel + +static inline int shader_is_vertex(const Context *ctx) +{ + return (ctx->shader_type == MOJOSHADER_TYPE_VERTEX); +} // shader_is_vertex + +static inline void pushback(Context *ctx) +{ + #if DEBUG_ASSEMBLER_PARSER + printf("ASSEMBLER PUSHBACK\n"); + #endif + assert(!ctx->pushedback); + ctx->pushedback = 1; +} // pushback + + +static Token nexttoken(Context *ctx) +{ + if (ctx->pushedback) + ctx->pushedback = 0; + else + { + while (1) + { + ctx->token = preprocessor_nexttoken(ctx->preprocessor, + &ctx->tokenlen, + &ctx->tokenval); + + if (preprocessor_outofmemory(ctx->preprocessor)) + { + ctx->tokenval = TOKEN_EOI; + ctx->token = NULL; + ctx->tokenlen = 0; + break; + } // if + + unsigned int line; + ctx->current_file = preprocessor_sourcepos(ctx->preprocessor,&line); + ctx->current_position = (int) line; + + if (ctx->tokenval == TOKEN_BAD_CHARS) + { + fail(ctx, "Bad characters in source file"); + continue; + } // else if + + else if (ctx->tokenval == TOKEN_PREPROCESSING_ERROR) + { + fail(ctx, ctx->token); + continue; + } // else if + + break; + } // while + } // else + + print_debug_token(ctx->token, ctx->tokenlen, ctx->tokenval); + return ctx->tokenval; +} // nexttoken + + +static void output_token_noswap(Context *ctx, const uint32 token) +{ + if (!isfail(ctx)) + { + buffer_append(ctx->output, &token, sizeof (token)); + + // We only need a list of these that grows throughout processing, and + // is flattened for reference at the end of the run, so we use a + // Buffer. It's sneaky! + unsigned int pos = 0; + const char *fname = preprocessor_sourcepos(ctx->preprocessor, &pos); + SourcePos srcpos; + memset(&srcpos, '\0', sizeof (SourcePos)); + srcpos.line = pos; + srcpos.filename = fname; // cached in preprocessor! + buffer_append(ctx->token_to_source, &srcpos, sizeof (SourcePos)); + } // if +} // output_token_noswap + + +static inline void output_token(Context *ctx, const uint32 token) +{ + output_token_noswap(ctx, SWAP32(token)); +} // output_token + + +static void output_comment_bytes(Context *ctx, const uint8 *buf, size_t len) +{ + if (len > (0xFFFF * 4)) // length is stored as token count, in 16 bits. + fail(ctx, "Comment field is too big"); + else if (!isfail(ctx)) + { + const uint32 tokencount = (len / 4) + ((len % 4) ? 1 : 0); + output_token(ctx, 0xFFFE | (tokencount << 16)); + while (len >= 4) + { + output_token_noswap(ctx, *((const uint32 *) buf)); + len -= 4; + buf += 4; + } // while + + if (len > 0) // handle spillover... + { + union { uint8 ui8[4]; uint32 ui32; } overflow; + overflow.ui32 = 0; + memcpy(overflow.ui8, buf, len); + output_token_noswap(ctx, overflow.ui32); + } // if + } // else if +} // output_comment_bytes + + +static inline void output_comment_string(Context *ctx, const char *str) +{ + output_comment_bytes(ctx, (const uint8 *) str, strlen(str)); +} // output_comment_string + + +static int require_comma(Context *ctx) +{ + const Token token = nexttoken(ctx); + if (token != ((Token) ',')) + { + fail(ctx, "Comma expected"); + return 0; + } // if + return 1; +} // require_comma + + +static int check_token_segment(Context *ctx, const char *str) +{ + // !!! FIXME: these are case-insensitive, right? + const size_t len = strlen(str); + if ( (ctx->tokenlen < len) || (strncasecmp(ctx->token, str, len) != 0) ) + return 0; + ctx->token += len; + ctx->tokenlen -= len; + return 1; +} // check_token_segment + + +static int check_token(Context *ctx, const char *str) +{ + const size_t len = strlen(str); + if ( (ctx->tokenlen != len) || (strncasecmp(ctx->token, str, len) != 0) ) + return 0; + ctx->token += len; + ctx->tokenlen = 0; + return 1; +} // check_token + + +static int ui32fromtoken(Context *ctx, uint32 *_val) +{ + unsigned int i; + for (i = 0; i < ctx->tokenlen; i++) + { + if ((ctx->token[i] < '0') || (ctx->token[i] > '9')) + break; + } // for + + if (i == 0) + { + *_val = 0; + return 0; + } // if + + const unsigned int len = i; + uint32 val = 0; + uint32 mult = 1; + while (i--) + { + val += ((uint32) (ctx->token[i] - '0')) * mult; + mult *= 10; + } // while + + ctx->token += len; + ctx->tokenlen -= len; + + *_val = val; + return 1; +} // ui32fromtoken + + +static int parse_register_name(Context *ctx, RegisterType *rtype, int *rnum) +{ + if (nexttoken(ctx) != TOKEN_IDENTIFIER) + { + fail(ctx, "Expected register"); + return 0; + } // if + + int neednum = 1; + int regnum = 0; + RegisterType regtype = REG_TYPE_TEMP; + + // Watch out for substrings! oDepth must be checked before oD, since + // the latter will match either case. + if (check_token_segment(ctx, "oDepth")) + { + regtype = REG_TYPE_DEPTHOUT; + neednum = 0; + } // else if + else if (check_token_segment(ctx, "vFace")) + { + regtype = REG_TYPE_MISCTYPE; + regnum = (int) MISCTYPE_TYPE_FACE; + neednum = 0; + } // else if + else if (check_token_segment(ctx, "vPos")) + { + regtype = REG_TYPE_MISCTYPE; + regnum = (int) MISCTYPE_TYPE_POSITION; + neednum = 0; + } // else if + else if (check_token_segment(ctx, "oPos")) + { + regtype = REG_TYPE_RASTOUT; + regnum = (int) RASTOUT_TYPE_POSITION; + neednum = 0; + } // else if + else if (check_token_segment(ctx, "oFog")) + { + regtype = REG_TYPE_RASTOUT; + regnum = (int) RASTOUT_TYPE_FOG; + neednum = 0; + } // else if + else if (check_token_segment(ctx, "oPts")) + { + regtype = REG_TYPE_RASTOUT; + regnum = (int) RASTOUT_TYPE_POINT_SIZE; + neednum = 0; + } // else if + else if (check_token_segment(ctx, "aL")) + { + regtype = REG_TYPE_LOOP; + neednum = 0; + } // else if + else if (check_token_segment(ctx, "oC")) + regtype = REG_TYPE_COLOROUT; + else if (check_token_segment(ctx, "oT")) + regtype = REG_TYPE_OUTPUT; + else if (check_token_segment(ctx, "oD")) + regtype = REG_TYPE_ATTROUT; + else if (check_token_segment(ctx, "r")) + regtype = REG_TYPE_TEMP; + else if (check_token_segment(ctx, "v")) + regtype = REG_TYPE_INPUT; + else if (check_token_segment(ctx, "c")) + regtype = REG_TYPE_CONST; + else if (check_token_segment(ctx, "i")) + regtype = REG_TYPE_CONSTINT; + else if (check_token_segment(ctx, "b")) + regtype = REG_TYPE_CONSTBOOL; + else if (check_token_segment(ctx, "s")) + regtype = REG_TYPE_SAMPLER; + else if (check_token_segment(ctx, "l")) + regtype = REG_TYPE_LABEL; + else if (check_token_segment(ctx, "p")) + regtype = REG_TYPE_PREDICATE; + else if (check_token_segment(ctx, "o")) + regtype = REG_TYPE_OUTPUT; + else if (check_token_segment(ctx, "a")) + regtype = REG_TYPE_ADDRESS; + else if (check_token_segment(ctx, "t")) + regtype = REG_TYPE_ADDRESS; + + //case REG_TYPE_TEMPFLOAT16: // !!! FIXME: don't know this asm string + + else + { + fail(ctx, "expected register type"); + regtype = REG_TYPE_CONST; + regnum = 0; + neednum = 0; + } // else + + // "c[5]" is the same as "c5", so if the token is done, see if next is '['. + if ((neednum) && (ctx->tokenlen == 0)) + { + const int tlen = ctx->tokenlen; // we need to protect this for later. + if (nexttoken(ctx) == ((Token) '[')) + neednum = 0; // don't need a number on register name itself. + pushback(ctx); + ctx->tokenlen = tlen; + } // if + + if (neednum) + { + uint32 ui32 = 0; + if (!ui32fromtoken(ctx, &ui32)) + fail(ctx, "Invalid register index"); + regnum = (int) ui32; + } // if + + // split up REG_TYPE_CONST + if (regtype == REG_TYPE_CONST) + { + if (regnum < 2048) + { + regtype = REG_TYPE_CONST; + regnum -= 0; + } // if + else if (regnum < 4096) + { + regtype = REG_TYPE_CONST2; + regnum -= 2048; + } // if + else if (regnum < 6144) + { + regtype = REG_TYPE_CONST3; + regnum -= 4096; + } // if + else if (regnum < 8192) + { + regtype = REG_TYPE_CONST4; + regnum -= 6144; + } // if + else + { + fail(ctx, "Invalid const register index"); + } // else + } // if + + *rtype = regtype; + *rnum = regnum; + + return 1; +} // parse_register_name + + +static void set_result_shift(Context *ctx, DestArgInfo *info, const int val) +{ + if (info->result_shift != 0) + fail(ctx, "Multiple result shift modifiers"); + info->result_shift = val; +} // set_result_shift + + +static inline int tokenbuf_overflow(Context *ctx) +{ + if ( ctx->tokenbufpos >= ((int) (STATICARRAYLEN(ctx->tokenbuf))) ) + { + fail(ctx, "Too many tokens"); + return 1; + } // if + + return 0; +} // tokenbuf_overflow + + +static int parse_destination_token(Context *ctx) +{ + DestArgInfo *info = &ctx->dest_arg; + memset(info, '\0', sizeof (DestArgInfo)); + + // parse_instruction_token() sets ctx->token to the end of the instruction + // so we can see if there are destination modifiers on the instruction + // itself... + + int invalid_modifier = 0; + + while ((ctx->tokenlen > 0) && (!invalid_modifier)) + { + if (check_token_segment(ctx, "_x2")) + set_result_shift(ctx, info, 0x1); + else if (check_token_segment(ctx, "_x4")) + set_result_shift(ctx, info, 0x2); + else if (check_token_segment(ctx, "_x8")) + set_result_shift(ctx, info, 0x3); + else if (check_token_segment(ctx, "_d8")) + set_result_shift(ctx, info, 0xD); + else if (check_token_segment(ctx, "_d4")) + set_result_shift(ctx, info, 0xE); + else if (check_token_segment(ctx, "_d2")) + set_result_shift(ctx, info, 0xF); + else if (check_token_segment(ctx, "_sat")) + info->result_mod |= MOD_SATURATE; + else if (check_token_segment(ctx, "_pp")) + info->result_mod |= MOD_PP; + else if (check_token_segment(ctx, "_centroid")) + info->result_mod |= MOD_CENTROID; + else + invalid_modifier = 1; + } // while + + if (invalid_modifier) + fail(ctx, "Invalid destination modifier"); + + // !!! FIXME: predicates. + if (nexttoken(ctx) == ((Token) '(')) + fail(ctx, "Predicates unsupported at this time"); // !!! FIXME: ... + + pushback(ctx); // parse_register_name calls nexttoken(). + + parse_register_name(ctx, &info->regtype, &info->regnum); + // parse_register_name() can't check this: dest regs might have modifiers. + if (ctx->tokenlen > 0) + fail(ctx, "invalid register name"); + + // !!! FIXME: can dest registers do relative addressing? + + int invalid_writemask = 0; + int implicit_writemask = 0; + if (nexttoken(ctx) != ((Token) '.')) + { + implicit_writemask = 1; + info->writemask = 0xF; + info->writemask0 = info->writemask1 = info->writemask2 = info->writemask3 = 1; + pushback(ctx); // no explicit writemask; do full mask. + } // if + + // !!! FIXME: Cg generates code with oDepth.z ... this is a bug, I think. + //else if (scalar_register(ctx->shader_type, info->regtype, info->regnum)) + else if ( (scalar_register(ctx->shader_type, info->regtype, info->regnum)) && (info->regtype != REG_TYPE_DEPTHOUT) ) + fail(ctx, "Writemask specified for scalar register"); + else if (nexttoken(ctx) != TOKEN_IDENTIFIER) + invalid_writemask = 1; + else + { + char tokenbytes[5] = { '\0', '\0', '\0', '\0', '\0' }; + const unsigned int tokenlen = ctx->tokenlen; + memcpy(tokenbytes, ctx->token, ((tokenlen < 4) ? tokenlen : 4)); + char *ptr = tokenbytes; + + if ((*ptr == 'r') || (*ptr == 'x')) { info->writemask0 = 1; ptr++; } + if ((*ptr == 'g') || (*ptr == 'y')) { info->writemask1 = 1; ptr++; } + if ((*ptr == 'b') || (*ptr == 'z')) { info->writemask2 = 1; ptr++; } + if ((*ptr == 'a') || (*ptr == 'w')) { info->writemask3 = 1; ptr++; } + + if (*ptr != '\0') + invalid_writemask = 1; + + info->writemask = ( ((info->writemask0 & 0x1) << 0) | + ((info->writemask1 & 0x1) << 1) | + ((info->writemask2 & 0x1) << 2) | + ((info->writemask3 & 0x1) << 3) ); + } // else + + if (invalid_writemask) + fail(ctx, "Invalid writemask"); + + // !!! FIXME: Cg generates code with oDepth.z ... this is a bug, I think. + if (info->regtype == REG_TYPE_DEPTHOUT) + { + if ( (!implicit_writemask) && ((info->writemask0 + info->writemask1 + + info->writemask2 + info->writemask3) > 1) ) + fail(ctx, "Writemask specified for scalar register"); + } // if + + info->orig_writemask = info->writemask; + + if (tokenbuf_overflow(ctx)) + return 1; + + ctx->tokenbuf[ctx->tokenbufpos++] = + ( ((((uint32) 1)) << 31) | + ((((uint32) info->regnum) & 0x7ff) << 0) | + ((((uint32) info->relative) & 0x1) << 13) | + ((((uint32) info->result_mod) & 0xF) << 20) | + ((((uint32) info->result_shift) & 0xF) << 24) | + ((((uint32) info->writemask) & 0xF) << 16) | + ((((uint32) info->regtype) & 0x7) << 28) | + ((((uint32) info->regtype) & 0x18) << 8) ); + + return 1; +} // parse_destination_token + + +static void set_source_mod(Context *ctx, const int negate, + const SourceMod norm, const SourceMod negated, + SourceMod *srcmod) +{ + if ( (*srcmod != SRCMOD_NONE) || (negate && (negated == SRCMOD_NONE)) ) + fail(ctx, "Incompatible source modifiers"); + else + *srcmod = ((negate) ? negated : norm); +} // set_source_mod + + +static int parse_source_token_maybe_relative(Context *ctx, const int relok) +{ + int retval = 1; + + if (tokenbuf_overflow(ctx)) + return 0; + + // mark this now, so optional relative addressing token is placed second. + uint32 *outtoken = &ctx->tokenbuf[ctx->tokenbufpos++]; + *outtoken = 0; + + SourceMod srcmod = SRCMOD_NONE; + int negate = 0; + Token token = nexttoken(ctx); + + if (token == ((Token) '!')) + srcmod = SRCMOD_NOT; + else if (token == ((Token) '-')) + negate = 1; + else if ( (token == TOKEN_INT_LITERAL) && (check_token(ctx, "1")) ) + { + if (nexttoken(ctx) != ((Token) '-')) + fail(ctx, "Unexpected token"); + else + srcmod = SRCMOD_COMPLEMENT; + } // else + else + { + pushback(ctx); + } // else + + RegisterType regtype; + int regnum; + parse_register_name(ctx, ®type, ®num); + + if (ctx->tokenlen == 0) + { + if (negate) + set_source_mod(ctx, negate, SRCMOD_NONE, SRCMOD_NEGATE, &srcmod); + } // if + else + { + assert(ctx->tokenlen > 0); + if (check_token_segment(ctx, "_bias")) + set_source_mod(ctx, negate, SRCMOD_BIAS, SRCMOD_BIASNEGATE, &srcmod); + else if (check_token_segment(ctx, "_bx2")) + set_source_mod(ctx, negate, SRCMOD_SIGN, SRCMOD_SIGNNEGATE, &srcmod); + else if (check_token_segment(ctx, "_x2")) + set_source_mod(ctx, negate, SRCMOD_X2, SRCMOD_X2NEGATE, &srcmod); + else if (check_token_segment(ctx, "_dz")) + set_source_mod(ctx, negate, SRCMOD_DZ, SRCMOD_NONE, &srcmod); + else if (check_token_segment(ctx, "_dw")) + set_source_mod(ctx, negate, SRCMOD_DW, SRCMOD_NONE, &srcmod); + else if (check_token_segment(ctx, "_abs")) + set_source_mod(ctx, negate, SRCMOD_ABS, SRCMOD_ABSNEGATE, &srcmod); + else + fail(ctx, "Invalid source modifier"); + } // else + + uint32 relative = 0; + if (nexttoken(ctx) != ((Token) '[')) + pushback(ctx); // not relative addressing? + else + { + if (!relok) + fail(ctx, "Relative addressing not permitted here."); + else + retval++; + + parse_source_token_maybe_relative(ctx, 0); + relative = 1; + + if (nexttoken(ctx) != ((Token) '+')) + pushback(ctx); + else + { + // !!! FIXME: maybe c3[a0.x + 5] is legal and becomes c[a0.x + 8] ? + if (regnum != 0) + fail(ctx, "Relative addressing with explicit register number."); + + uint32 ui32 = 0; + if ( (nexttoken(ctx) != TOKEN_INT_LITERAL) || + (!ui32fromtoken(ctx, &ui32)) || + (ctx->tokenlen != 0) ) + { + fail(ctx, "Invalid relative addressing offset"); + } // if + regnum += (int) ui32; + } // else + + if (nexttoken(ctx) != ((Token) ']')) + fail(ctx, "Expected ']'"); + } // else + + int invalid_swizzle = 0; + uint32 swizzle = 0; + if (nexttoken(ctx) != ((Token) '.')) + { + swizzle = 0xE4; // 0xE4 == 11100100 ... 0 1 2 3. No swizzle. + pushback(ctx); // no explicit writemask; do full mask. + } // if + else if (scalar_register(ctx->shader_type, regtype, regnum)) + fail(ctx, "Swizzle specified for scalar register"); + else if (nexttoken(ctx) != TOKEN_IDENTIFIER) + invalid_swizzle = 1; + else + { + char tokenbytes[5] = { '\0', '\0', '\0', '\0', '\0' }; + const unsigned int tokenlen = ctx->tokenlen; + memcpy(tokenbytes, ctx->token, ((tokenlen < 4) ? tokenlen : 4)); + + // deal with shortened form (.x = .xxxx, etc). + if (tokenlen == 1) + tokenbytes[1] = tokenbytes[2] = tokenbytes[3] = tokenbytes[0]; + else if (tokenlen == 2) + tokenbytes[2] = tokenbytes[3] = tokenbytes[1]; + else if (tokenlen == 3) + tokenbytes[3] = tokenbytes[2]; + else if (tokenlen != 4) + invalid_swizzle = 1; + tokenbytes[4] = '\0'; + + uint32 val = 0; + int i; + for (i = 0; i < 4; i++) + { + const int component = (int) tokenbytes[i]; + switch (component) + { + case 'r': case 'x': val = 0; break; + case 'g': case 'y': val = 1; break; + case 'b': case 'z': val = 2; break; + case 'a': case 'w': val = 3; break; + default: invalid_swizzle = 1; break; + } // switch + swizzle |= (val << (i * 2)); + } // for + } // else + + if (invalid_swizzle) + fail(ctx, "Invalid swizzle"); + + *outtoken = ( ((((uint32) 1)) << 31) | + ((((uint32) regnum) & 0x7ff) << 0) | + ((((uint32) relative) & 0x1) << 13) | + ((((uint32) swizzle) & 0xFF) << 16) | + ((((uint32) srcmod) & 0xF) << 24) | + ((((uint32) regtype) & 0x7) << 28) | + ((((uint32) regtype) & 0x18) << 8) ); + + return retval; +} // parse_source_token_maybe_relative + + +static inline int parse_source_token(Context *ctx) +{ + return parse_source_token_maybe_relative(ctx, 1); +} // parse_source_token + + +static int parse_args_NULL(Context *ctx) +{ + return 1; +} // parse_args_NULL + + +static int parse_num(Context *ctx, const int floatok, uint32 *value) +{ + union { float f; int32 si32; uint32 ui32; } cvt; + int negative = 0; + Token token = nexttoken(ctx); + + if (token == ((Token) '-')) + { + negative = 1; + token = nexttoken(ctx); + } // if + + if (token == TOKEN_INT_LITERAL) + { + int d = 0; + sscanf(ctx->token, "%d", &d); + if (floatok) + cvt.f = (float) ((negative) ? -d : d); + else + cvt.si32 = (int32) ((negative) ? -d : d); + } // if + else if (token == TOKEN_FLOAT_LITERAL) + { + if (!floatok) + { + fail(ctx, "Expected whole number"); + *value = 0; + return 0; + } // if + sscanf(ctx->token, "%f", &cvt.f); + if (negative) + cvt.f = -cvt.f; + } // if + else + { + fail(ctx, "Expected number"); + *value = 0; + return 0; + } // else + + *value = cvt.ui32; + return 1; +} // parse_num + + +static int parse_args_DEFx(Context *ctx, const int isflt) +{ + parse_destination_token(ctx); + require_comma(ctx); + parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]); + require_comma(ctx); + parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]); + require_comma(ctx); + parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]); + require_comma(ctx); + parse_num(ctx, isflt, &ctx->tokenbuf[ctx->tokenbufpos++]); + return 6; +} // parse_args_DEFx + + +static int parse_args_DEF(Context *ctx) +{ + return parse_args_DEFx(ctx, 1); +} // parse_args_DEF + + +static int parse_args_DEFI(Context *ctx) +{ + return parse_args_DEFx(ctx, 0); +} // parse_args_DEFI + + +static int parse_args_DEFB(Context *ctx) +{ + parse_destination_token(ctx); + require_comma(ctx); + + // !!! FIXME: do a TOKEN_TRUE and TOKEN_FALSE? Is this case-sensitive? + const Token token = nexttoken(ctx); + + int bad = 0; + if (token != TOKEN_IDENTIFIER) + bad = 1; + else if (check_token_segment(ctx, "true")) + ctx->tokenbuf[ctx->tokenbufpos++] = 1; + else if (check_token_segment(ctx, "false")) + ctx->tokenbuf[ctx->tokenbufpos++] = 0; + else + bad = 1; + + if (ctx->tokenlen != 0) + bad = 1; + + if (bad) + fail(ctx, "Expected 'true' or 'false'"); + + return 3; +} // parse_args_DEFB + + +static int parse_dcl_usage(Context *ctx, uint32 *val, int *issampler) +{ + size_t i; + static const char *samplerusagestrs[] = { "_2d", "_cube", "_volume" }; + static const char *usagestrs[] = { + "_position", "_blendweight", "_blendindices", "_normal", "_psize", + "_texcoord", "_tangent", "_binormal", "_tessfactor", "_positiont", + "_color", "_fog", "_depth", "_sample" + }; + + for (i = 0; i < STATICARRAYLEN(usagestrs); i++) + { + if (check_token_segment(ctx, usagestrs[i])) + { + *issampler = 0; + *val = i; + return 1; + } // if + } // for + + for (i = 0; i < STATICARRAYLEN(samplerusagestrs); i++) + { + if (check_token_segment(ctx, samplerusagestrs[i])) + { + *issampler = 1; + *val = i + 2; + return 1; + } // if + } // for + + *issampler = 0; + *val = 0; + return 0; +} // parse_dcl_usage + + +static int parse_args_DCL(Context *ctx) +{ + int issampler = 0; + uint32 usage = 0; + uint32 index = 0; + + ctx->tokenbufpos++; // save a spot for the usage/index token. + ctx->tokenbuf[0] = 0; + + // parse_instruction_token() sets ctx->token to the end of the instruction + // so we can see if there are destination modifiers on the instruction + // itself... + + if (parse_dcl_usage(ctx, &usage, &issampler)) + { + if ((ctx->tokenlen > 0) && (*ctx->token != '_')) + { + if (!ui32fromtoken(ctx, &index)) + fail(ctx, "Expected usage index"); + } // if + } // if + + parse_destination_token(ctx); + + const int samplerreg = (ctx->dest_arg.regtype == REG_TYPE_SAMPLER); + if (issampler != samplerreg) + fail(ctx, "Invalid usage"); + else if (samplerreg) + ctx->tokenbuf[0] = (usage << 27) | 0x80000000; + else + ctx->tokenbuf[0] = usage | (index << 16) | 0x80000000; + + return 3; +} // parse_args_DCL + + +static int parse_args_D(Context *ctx) +{ + int retval = 1; + retval += parse_destination_token(ctx); + return retval; +} // parse_args_D + + +static int parse_args_S(Context *ctx) +{ + int retval = 1; + retval += parse_source_token(ctx); + return retval; +} // parse_args_S + + +static int parse_args_SS(Context *ctx) +{ + int retval = 1; + retval += parse_source_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + return retval; +} // parse_args_SS + + +static int parse_args_DS(Context *ctx) +{ + int retval = 1; + retval += parse_destination_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + return retval; +} // parse_args_DS + + +static int parse_args_DSS(Context *ctx) +{ + int retval = 1; + retval += parse_destination_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + return retval; +} // parse_args_DSS + + +static int parse_args_DSSS(Context *ctx) +{ + int retval = 1; + retval += parse_destination_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + return retval; +} // parse_args_DSSS + + +static int parse_args_DSSSS(Context *ctx) +{ + int retval = 1; + retval += parse_destination_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + require_comma(ctx); + retval += parse_source_token(ctx); + return retval; +} // parse_args_DSSSS + + +static int parse_args_SINCOS(Context *ctx) +{ + // this opcode needs extra registers for sm2 and lower. + if (!shader_version_atleast(ctx, 3, 0)) + return parse_args_DSSS(ctx); + return parse_args_DS(ctx); +} // parse_args_SINCOS + + +static int parse_args_TEXCRD(Context *ctx) +{ + // added extra register in ps_1_4. + if (shader_version_atleast(ctx, 1, 4)) + return parse_args_DS(ctx); + return parse_args_D(ctx); +} // parse_args_TEXCRD + + +static int parse_args_TEXLD(Context *ctx) +{ + // different registers in px_1_3, ps_1_4, and ps_2_0! + if (shader_version_atleast(ctx, 2, 0)) + return parse_args_DSS(ctx); + else if (shader_version_atleast(ctx, 1, 4)) + return parse_args_DS(ctx); + return parse_args_D(ctx); +} // parse_args_TEXLD + + + +// one args function for each possible sequence of opcode arguments. +typedef int (*args_function)(Context *ctx); + +// Lookup table for instruction opcodes... +typedef struct +{ + const char *opcode_string; + args_function parse_args; +} Instruction; + + +static const Instruction instructions[] = +{ + #define INSTRUCTION_STATE(op, opstr, s, a, t) { opstr, parse_args_##a }, + #define INSTRUCTION(op, opstr, slots, a, t) { opstr, parse_args_##a }, + #define MOJOSHADER_DO_INSTRUCTION_TABLE 1 + #include "mojoshader_internal.h" + #undef MOJOSHADER_DO_INSTRUCTION_TABLE + #undef INSTRUCTION + #undef INSTRUCTION_STATE +}; + + +static int parse_condition(Context *ctx, uint32 *controls) +{ + static const char *comps[] = { "_gt", "_eq", "_ge", "_lt", "_ne", "_le" }; + size_t i; + + if (ctx->tokenlen >= 3) + { + for (i = 0; i < STATICARRAYLEN(comps); i++) + { + if (check_token_segment(ctx, comps[i])) + { + *controls = (uint32) (i + 1); + return 1; + } // if + } // for + } // if + + return 0; +} // parse_condition + + +static int parse_instruction_token(Context *ctx, Token token) +{ + int coissue = 0; + int predicated = 0; + + if (token == ((Token) '+')) + { + coissue = 1; + token = nexttoken(ctx); + } // if + + if (token != TOKEN_IDENTIFIER) + { + fail(ctx, "Expected instruction"); + return 0; + } // if + + uint32 controls = 0; + uint32 opcode = OPCODE_TEXLD; + const char *origtoken = ctx->token; + const unsigned int origtokenlen = ctx->tokenlen; + + // This might need to be TEXLD instead of TEXLDP. + if (check_token_segment(ctx, "TEXLDP")) + controls = CONTROL_TEXLDP; + + // This might need to be TEXLD instead of TEXLDB. + else if (check_token_segment(ctx, "TEXLDB")) + controls = CONTROL_TEXLDB; + + else // find the instruction. + { + size_t i; + for (i = 0; i < STATICARRAYLEN(instructions); i++) + { + const char *opcode_string = instructions[i].opcode_string; + if (opcode_string == NULL) + continue; // skip this. + else if (!check_token_segment(ctx, opcode_string)) + continue; // not us. + else if ((ctx->tokenlen > 0) && (*ctx->token != '_')) + { + ctx->token = origtoken; + ctx->tokenlen = origtokenlen; + continue; // not the match: TEXLD when we wanted TEXLDL, etc. + } // if + + break; // found it! + } // for + + opcode = (uint32) i; + + // This might need to be IFC instead of IF. + if (opcode == OPCODE_IF) + { + if (parse_condition(ctx, &controls)) + opcode = OPCODE_IFC; + } // if + + // This might need to be BREAKC instead of BREAK. + else if (opcode == OPCODE_BREAK) + { + if (parse_condition(ctx, &controls)) + opcode = OPCODE_BREAKC; + } // else if + + // SETP has a conditional code, always. + else if (opcode == OPCODE_SETP) + { + if (!parse_condition(ctx, &controls)) + fail(ctx, "SETP requires a condition"); + } // else if + } // else + + if ( (opcode == STATICARRAYLEN(instructions)) || + ((ctx->tokenlen > 0) && (ctx->token[0] != '_')) ) + { + char opstr[32]; + const int len = Min(sizeof (opstr) - 1, origtokenlen); + memcpy(opstr, origtoken, len); + opstr[len] = '\0'; + failf(ctx, "Unknown instruction '%s'", opstr); + return 0; + } // if + + const Instruction *instruction = &instructions[opcode]; + + // !!! FIXME: predicated instructions + + ctx->tokenbufpos = 0; + + const int tokcount = instruction->parse_args(ctx); + + // insttoks bits are reserved and should be zero if < SM2. + const uint32 insttoks = shader_version_atleast(ctx, 2, 0) ? tokcount-1 : 0; + + // write out the instruction token. + output_token(ctx, ((opcode & 0xFFFF) << 0) | + ((controls & 0xFF) << 16) | + ((insttoks & 0xF) << 24) | + ((coissue) ? 0x40000000 : 0x00000000) | + ((predicated) ? 0x10000000 : 0x00000000) ); + + // write out the argument tokens. + int i; + for (i = 0; i < (tokcount-1); i++) + output_token(ctx, ctx->tokenbuf[i]); + + return 1; +} // parse_instruction_token + + +static void parse_version_token(Context *ctx) +{ + int bad = 0; + int dot_form = 0; + uint32 shader_type = 0; + + if (nexttoken(ctx) != TOKEN_IDENTIFIER) + bad = 1; + else if (check_token_segment(ctx, "vs")) + { + ctx->shader_type = MOJOSHADER_TYPE_VERTEX; + shader_type = 0xFFFE; + } // if + else if (check_token_segment(ctx, "ps")) + { + ctx->shader_type = MOJOSHADER_TYPE_PIXEL; + shader_type = 0xFFFF; + } // if + else + { + // !!! FIXME: geometry shaders? + bad = 1; + } // else + + dot_form = ((!bad) && (ctx->tokenlen == 0)); // it's in xs.x.x form? + + uint32 major = 0; + uint32 minor = 0; + + if (dot_form) + { + Token t = TOKEN_UNKNOWN; + + if (!bad) + { + t = nexttoken(ctx); + // stupid lexer sees "vs.2.0" and makes the ".2" into a float. + if (t == ((Token) '.')) + t = nexttoken(ctx); + else + { + if ((t != TOKEN_FLOAT_LITERAL) || (ctx->token[0] != '.')) + bad = 1; + else + { + ctx->tokenval = t = TOKEN_INT_LITERAL; + ctx->token++; + ctx->tokenlen--; + } // else + } // else + } // if + + if (!bad) + { + if (t != TOKEN_INT_LITERAL) + bad = 1; + else if (!ui32fromtoken(ctx, &major)) + bad = 1; + } // if + + if (!bad) + { + t = nexttoken(ctx); + // stupid lexer sees "vs.2.0" and makes the ".2" into a float. + if (t == ((Token) '.')) + t = nexttoken(ctx); + else + { + if ((t != TOKEN_FLOAT_LITERAL) || (ctx->token[0] != '.')) + bad = 1; + else + { + ctx->tokenval = t = TOKEN_INT_LITERAL; + ctx->token++; + ctx->tokenlen--; + } // else + } // else + } // if + + if (!bad) + { + if ((t == TOKEN_INT_LITERAL) && (ui32fromtoken(ctx, &minor))) + ; // good to go. + else if ((t == TOKEN_IDENTIFIER) && (check_token_segment(ctx, "x"))) + minor = 1; + else if ((t == TOKEN_IDENTIFIER) && (check_token_segment(ctx, "sw"))) + minor = 255; + else + bad = 1; + } // if + } // if + else + { + if (!check_token_segment(ctx, "_")) + bad = 1; + else if (!ui32fromtoken(ctx, &major)) + bad = 1; + else if (!check_token_segment(ctx, "_")) + bad = 1; + else if (check_token_segment(ctx, "x")) + minor = 1; + else if (check_token_segment(ctx, "sw")) + minor = 255; + else if (!ui32fromtoken(ctx, &minor)) + bad = 1; + } // else + + if ((!bad) && (ctx->tokenlen != 0)) + bad = 1; + + if (bad) + fail(ctx, "Expected valid version string"); + + ctx->major_ver = major; + ctx->minor_ver = minor; + + ctx->version_token = (shader_type << 16) | (major << 8) | (minor << 0); + output_token(ctx, ctx->version_token); +} // parse_version_token + + +static void parse_phase_token(Context *ctx) +{ + output_token(ctx, 0x0000FFFD); // phase token always 0x0000FFFD. +} // parse_phase_token + + +static void parse_end_token(Context *ctx) +{ + // We don't emit the end token bits here, since it's valid for a shader + // to not specify an "end" string at all; it's implicit, in that case. + // Instead, we make sure if we see "end" that it's the last thing we see. + if (nexttoken(ctx) != TOKEN_EOI) + fail(ctx, "Content after END"); +} // parse_end_token + + +static void parse_token(Context *ctx, const Token token) +{ + if (token != TOKEN_IDENTIFIER) + parse_instruction_token(ctx, token); // might be a coissue '+', etc. + else + { + if (check_token(ctx, "end")) + parse_end_token(ctx); + else if (check_token(ctx, "phase")) + parse_phase_token(ctx); + else + parse_instruction_token(ctx, token); + } // if +} // parse_token + + +static void destroy_context(Context *ctx) +{ + if (ctx != NULL) + { + MOJOSHADER_free f = ((ctx->free != NULL) ? ctx->free : MOJOSHADER_internal_free); + void *d = ctx->malloc_data; + preprocessor_end(ctx->preprocessor); + errorlist_destroy(ctx->errors); + buffer_destroy(ctx->ctab); + buffer_destroy(ctx->token_to_source); + buffer_destroy(ctx->output); + f(ctx, d); + } // if +} // destroy_context + + +static Context *build_context(const char *filename, + const char *source, unsigned int sourcelen, + const MOJOSHADER_preprocessorDefine *defines, + unsigned int define_count, + MOJOSHADER_includeOpen include_open, + MOJOSHADER_includeClose include_close, + MOJOSHADER_malloc m, MOJOSHADER_free f, void *d) +{ + if (!m) m = MOJOSHADER_internal_malloc; + if (!f) f = MOJOSHADER_internal_free; + if (!include_open) include_open = MOJOSHADER_internal_include_open; + if (!include_close) include_close = MOJOSHADER_internal_include_close; + + Context *ctx = (Context *) m(sizeof (Context), d); + if (ctx == NULL) + return NULL; + + memset(ctx, '\0', sizeof (Context)); + ctx->malloc = m; + ctx->free = f; + ctx->malloc_data = d; + ctx->current_position = MOJOSHADER_POSITION_BEFORE; + + const size_t outblk = sizeof (uint32) * 4 * 64; // 64 4-token instrs. + ctx->output = buffer_create(outblk, MallocBridge, FreeBridge, ctx); + if (ctx->output == NULL) + goto build_context_failed; + + const size_t mapblk = sizeof (SourcePos) * 4 * 64; // 64 * 4-tokens. + ctx->token_to_source = buffer_create(mapblk, MallocBridge, FreeBridge, ctx); + if (ctx->token_to_source == NULL) + goto build_context_failed; + + ctx->errors = errorlist_create(MallocBridge, FreeBridge, ctx); + if (ctx->errors == NULL) + goto build_context_failed; + + ctx->preprocessor = preprocessor_start(filename, source, sourcelen, + include_open, include_close, + defines, define_count, 1, + MallocBridge, FreeBridge, ctx); + + if (ctx->preprocessor == NULL) + goto build_context_failed; + + return ctx; + +build_context_failed: // ctx is allocated and zeroed before this is called. + destroy_context(ctx); + return NULL; +} // build_context + + +static const MOJOSHADER_parseData *build_failed_assembly(Context *ctx) +{ + assert(isfail(ctx)); + + if (ctx->out_of_memory) + return &MOJOSHADER_out_of_mem_data; + + MOJOSHADER_parseData *retval = NULL; + retval = (MOJOSHADER_parseData*) Malloc(ctx, sizeof(MOJOSHADER_parseData)); + if (retval == NULL) + return &MOJOSHADER_out_of_mem_data; + + memset(retval, '\0', sizeof (MOJOSHADER_parseData)); + retval->malloc = (ctx->malloc == MOJOSHADER_internal_malloc) ? NULL : ctx->malloc; + retval->free = (ctx->free == MOJOSHADER_internal_free) ? NULL : ctx->free; + retval->malloc_data = ctx->malloc_data; + + retval->error_count = errorlist_count(ctx->errors); + retval->errors = errorlist_flatten(ctx->errors); + + if (ctx->out_of_memory) + { + Free(ctx, retval->errors); + Free(ctx, retval); + return &MOJOSHADER_out_of_mem_data; + } // if + + return retval; +} // build_failed_assembly + + +static uint32 add_ctab_bytes(Context *ctx, const uint8 *bytes, const size_t len) +{ + if (isfail(ctx)) + return 0; + + const size_t extra = CTAB_SIZE + sizeof (uint32); + const ssize_t pos = buffer_find(ctx->ctab, extra, bytes, len); + if (pos >= 0) // blob is already in here. + return ((uint32) pos) - sizeof (uint32); + + // add it to the byte pile... + const uint32 retval = ((uint32) buffer_size(ctx->ctab)) - sizeof (uint32); + buffer_append(ctx->ctab, bytes, len); + return retval; +} // add_ctab_bytes + + +static inline uint32 add_ctab_string(Context *ctx, const char *str) +{ + return add_ctab_bytes(ctx, (const uint8 *) str, strlen(str) + 1); +} // add_ctab_string + + +static uint32 add_ctab_typeinfo(Context *ctx, const MOJOSHADER_symbolTypeInfo *info); + +static uint32 add_ctab_members(Context *ctx, const MOJOSHADER_symbolTypeInfo *info) +{ + unsigned int i; + const size_t len = info->member_count * CMEMBERINFO_SIZE; + uint8 *bytes = (uint8 *) Malloc(ctx, len); + if (bytes == NULL) + return 0; + + union { uint8 *ui8; uint16 *ui16; uint32 *ui32; } ptr; + ptr.ui8 = bytes; + for (i = 0; i < info->member_count; i++) + { + const MOJOSHADER_symbolStructMember *member = &info->members[i]; + *(ptr.ui32++) = SWAP32(add_ctab_string(ctx, member->name)); + *(ptr.ui32++) = SWAP32(add_ctab_typeinfo(ctx, &member->info)); + } // for + + const uint32 retval = add_ctab_bytes(ctx, bytes, len); + Free(ctx, bytes); + return retval; +} // add_ctab_members + + +static uint32 add_ctab_typeinfo(Context *ctx, const MOJOSHADER_symbolTypeInfo *info) +{ + uint8 bytes[CTYPEINFO_SIZE]; + union { uint8 *ui8; uint16 *ui16; uint32 *ui32; } ptr; + ptr.ui8 = bytes; + + *(ptr.ui16++) = SWAP16((uint16) info->parameter_class); + *(ptr.ui16++) = SWAP16((uint16) info->parameter_type); + *(ptr.ui16++) = SWAP16((uint16) info->rows); + *(ptr.ui16++) = SWAP16((uint16) info->columns); + *(ptr.ui16++) = SWAP16((uint16) info->elements); + *(ptr.ui16++) = SWAP16((uint16) info->member_count); + *(ptr.ui32++) = SWAP32(add_ctab_members(ctx, info)); + + return add_ctab_bytes(ctx, bytes, sizeof (bytes)); +} // add_ctab_typeinfo + + +static uint32 add_ctab_info(Context *ctx, const MOJOSHADER_symbol *symbols, + const unsigned int symbol_count) +{ + unsigned int i; + const size_t len = symbol_count * CINFO_SIZE; + uint8 *bytes = (uint8 *) Malloc(ctx, len); + if (bytes == NULL) + return 0; + + union { uint8 *ui8; uint16 *ui16; uint32 *ui32; } ptr; + ptr.ui8 = bytes; + for (i = 0; i < symbol_count; i++) + { + const MOJOSHADER_symbol *sym = &symbols[i]; + *(ptr.ui32++) = SWAP32(add_ctab_string(ctx, sym->name)); + *(ptr.ui16++) = SWAP16((uint16) sym->register_set); + *(ptr.ui16++) = SWAP16((uint16) sym->register_index); + *(ptr.ui16++) = SWAP16((uint16) sym->register_count); + *(ptr.ui16++) = SWAP16(0); // reserved + *(ptr.ui32++) = SWAP32(add_ctab_typeinfo(ctx, &sym->info)); + *(ptr.ui32++) = SWAP32(0); // !!! FIXME: default value. + } // for + + const uint32 retval = add_ctab_bytes(ctx, bytes, len); + Free(ctx, bytes); + return retval; +} // add_ctab_info + + +static void output_ctab(Context *ctx, const MOJOSHADER_symbol *symbols, + unsigned int symbol_count, const char *creator) +{ + const size_t tablelen = CTAB_SIZE + sizeof (uint32); + + ctx->ctab = buffer_create(256, MallocBridge, FreeBridge, ctx); + if (ctx->ctab == NULL) + return; // out of memory. + + uint32 *table = (uint32 *) buffer_reserve(ctx->ctab, tablelen); + if (table == NULL) + { + buffer_destroy(ctx->ctab); + ctx->ctab = NULL; + return; // out of memory. + } // if + + *(table++) = SWAP32(CTAB_ID); + *(table++) = SWAP32(CTAB_SIZE); + *(table++) = SWAP32(add_ctab_string(ctx, creator)); + *(table++) = SWAP32(ctx->version_token); + *(table++) = SWAP32(((uint32) symbol_count)); + *(table++) = SWAP32(add_ctab_info(ctx, symbols, symbol_count)); + *(table++) = SWAP32(0); // build flags. + *(table++) = SWAP32(add_ctab_string(ctx, "")); // !!! FIXME: target? + + const size_t ctablen = buffer_size(ctx->ctab); + uint8 *buf = (uint8 *) buffer_flatten(ctx->ctab); + if (buf != NULL) + { + output_comment_bytes(ctx, buf, ctablen); + Free(ctx, buf); + } // if + + buffer_destroy(ctx->ctab); + ctx->ctab = NULL; +} // output_ctab + + +static void output_comments(Context *ctx, const char **comments, + unsigned int comment_count, + const MOJOSHADER_symbol *symbols, + unsigned int symbol_count) +{ + if (isfail(ctx)) + return; + + // make error messages sane if CTAB fails, etc. + const char *prev_fname = ctx->current_file; + const int prev_position = ctx->current_position; + ctx->current_file = NULL; + ctx->current_position = MOJOSHADER_POSITION_BEFORE; + + const char *creator = "MojoShader revision " MOJOSHADER_CHANGESET; + if (symbol_count > 0) + output_ctab(ctx, symbols, symbol_count, creator); + else + output_comment_string(ctx, creator); + + unsigned int i; + for (i = 0; i < comment_count; i++) + output_comment_string(ctx, comments[i]); + + ctx->current_file = prev_fname; + ctx->current_position = prev_position; +} // output_comments + + +static const MOJOSHADER_parseData *build_final_assembly(Context *ctx) +{ + if (isfail(ctx)) + return build_failed_assembly(ctx); + + // get the final bytecode! + const unsigned int output_len = (unsigned int) buffer_size(ctx->output); + unsigned char *bytecode = (unsigned char *) buffer_flatten(ctx->output); + buffer_destroy(ctx->output); + ctx->output = NULL; + + if (bytecode == NULL) + return build_failed_assembly(ctx); + + // This validates the shader; there are lots of things that are + // invalid, but will successfully parse in the assembler, + // generating bad bytecode; this will catch them without us + // having to duplicate most of the validation here. + // It also saves us the trouble of duplicating all the other work, + // like setting up the uniforms list, etc. + MOJOSHADER_parseData *retval = (MOJOSHADER_parseData *) + MOJOSHADER_parse(MOJOSHADER_PROFILE_BYTECODE, + bytecode, output_len, NULL, 0, + ctx->malloc, ctx->free, ctx->malloc_data); + Free(ctx, bytecode); + + SourcePos *token_to_src = NULL; + if (retval->error_count > 0) + token_to_src = (SourcePos *) buffer_flatten(ctx->token_to_source); + buffer_destroy(ctx->token_to_source); + ctx->token_to_source = NULL; + + if (retval->error_count > 0) + { + if (token_to_src == NULL) + { + assert(ctx->out_of_memory); + MOJOSHADER_freeParseData(retval); + return build_failed_assembly(ctx); + } // if + + // on error, map the bytecode back to a line number. + int i; + for (i = 0; i < retval->error_count; i++) + { + MOJOSHADER_error *error = &retval->errors[i]; + if (error->error_position >= 0) + { + assert(retval != &MOJOSHADER_out_of_mem_data); + assert((error->error_position % sizeof (uint32)) == 0); + + const size_t pos = error->error_position / sizeof(uint32); + if (pos >= output_len) + error->error_position = -1; // oh well. + else + { + const SourcePos *srcpos = &token_to_src[pos]; + Free(ctx, (void *) error->filename); + char *fname = NULL; + if (srcpos->filename != NULL) + fname = StrDup(ctx, srcpos->filename); + error->error_position = srcpos->line; + error->filename = fname; // may be NULL, that's okay. + } // else + } // if + } // for + + Free(ctx, token_to_src); + } // if + + return retval; +} // build_final_assembly + + +// API entry point... + +const MOJOSHADER_parseData *MOJOSHADER_assemble(const char *filename, + const char *source, unsigned int sourcelen, + const char **comments, unsigned int comment_count, + const MOJOSHADER_symbol *symbols, + unsigned int symbol_count, + const MOJOSHADER_preprocessorDefine *defines, + unsigned int define_count, + MOJOSHADER_includeOpen include_open, + MOJOSHADER_includeClose include_close, + MOJOSHADER_malloc m, MOJOSHADER_free f, void *d) +{ + const MOJOSHADER_parseData *retval = NULL; + Context *ctx = NULL; + + if ( ((m == NULL) && (f != NULL)) || ((m != NULL) && (f == NULL)) ) + return &MOJOSHADER_out_of_mem_data; // supply both or neither. + + ctx = build_context(filename, source, sourcelen, defines, define_count, + include_open, include_close, m, f, d); + if (ctx == NULL) + return &MOJOSHADER_out_of_mem_data; + + // Version token always comes first. + parse_version_token(ctx); + output_comments(ctx, comments, comment_count, symbols, symbol_count); + + // parse out the rest of the tokens after the version token... + Token token; + while ((token = nexttoken(ctx)) != TOKEN_EOI) + parse_token(ctx, token); + + ctx->current_file = NULL; + ctx->current_position = MOJOSHADER_POSITION_AFTER; + + output_token(ctx, 0x0000FFFF); // end token always 0x0000FFFF. + + retval = build_final_assembly(ctx); + destroy_context(ctx); + return retval; +} // MOJOSHADER_assemble + +// end of mojoshader_assembler.c ... + |