/** * 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 needs to be split into separate source files: // !!! FIXME: parse, AST, IR, etc. The problem is we need to deal with the // !!! FIXME: "Context" struct being passed around everywhere. #define __MOJOSHADER_INTERNAL__ 1 #include "mojoshader_internal.h" #if DEBUG_COMPILER_PARSER #define LEMON_SUPPORT_TRACING 1 #endif // !!! FIXME: I'd like to lose this. It's really inefficient. Just keep a // !!! FIXME: (tail) on these list structures instead? #define REVERSE_LINKED_LIST(typ, head) { \ if ((head) && (head->next)) { \ typ *tmp = NULL; \ typ *tmp1 = NULL; \ while (head != NULL) { \ tmp = head; \ head = head->next; \ tmp->next = tmp1; \ tmp1 = tmp; \ } \ head = tmp; \ } \ } static inline int operator_is_unary(const MOJOSHADER_astNodeType op) { return ( (op > MOJOSHADER_AST_OP_START_RANGE_UNARY) && (op < MOJOSHADER_AST_OP_END_RANGE_UNARY) ); } // operator_is_unary static inline int operator_is_binary(const MOJOSHADER_astNodeType op) { return ( (op > MOJOSHADER_AST_OP_START_RANGE_BINARY) && (op < MOJOSHADER_AST_OP_END_RANGE_BINARY) ); } // operator_is_binary static inline int operator_is_ternary(const MOJOSHADER_astNodeType op) { return ( (op > MOJOSHADER_AST_OP_START_RANGE_TERNARY) && (op < MOJOSHADER_AST_OP_END_RANGE_TERNARY) ); } // operator_is_ternary typedef union TokenData { int64 i64; double dbl; const char *string; const MOJOSHADER_astDataType *datatype; } TokenData; // This tracks data types and variables, and notes when they enter/leave scope. typedef struct SymbolScope { const char *symbol; const MOJOSHADER_astDataType *datatype; struct SymbolScope *next; } SymbolScope; typedef struct SymbolMap { HashTable *hash; SymbolScope *scope; } SymbolMap; // Compile state, passed around all over the place. typedef struct Context { int isfail; int out_of_memory; MOJOSHADER_malloc malloc; MOJOSHADER_free free; void *malloc_data; ErrorList *errors; ErrorList *warnings; StringCache *strcache; const char *sourcefile; // current source file that we're parsing. unsigned int sourceline; // current line in sourcefile that we're parsing. SymbolMap usertypes; SymbolMap variables; MOJOSHADER_astNode *ast; // Abstract Syntax Tree const char *source_profile; // Cache intrinsic types for fast lookup and consistent pointer values. MOJOSHADER_astDataType dt_bool; MOJOSHADER_astDataType dt_int; MOJOSHADER_astDataType dt_uint; MOJOSHADER_astDataType dt_float; MOJOSHADER_astDataType dt_float_snorm; MOJOSHADER_astDataType dt_float_unorm; MOJOSHADER_astDataType dt_half; MOJOSHADER_astDataType dt_double; MOJOSHADER_astDataType dt_string; MOJOSHADER_astDataType dt_sampler1d; MOJOSHADER_astDataType dt_sampler2d; MOJOSHADER_astDataType dt_sampler3d; MOJOSHADER_astDataType dt_samplercube; MOJOSHADER_astDataType dt_samplerstate; MOJOSHADER_astDataType dt_samplercompstate; MOJOSHADER_astDataType dt_buf_bool; MOJOSHADER_astDataType dt_buf_int; MOJOSHADER_astDataType dt_buf_uint; MOJOSHADER_astDataType dt_buf_half; MOJOSHADER_astDataType dt_buf_float; MOJOSHADER_astDataType dt_buf_double; MOJOSHADER_astDataType dt_buf_float_snorm; MOJOSHADER_astDataType dt_buf_float_unorm; Buffer *garbage; // this is sort of hacky. } 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->sourcefile, ctx->sourceline, fmt, ap); va_end(ap); } // failf static inline void fail(Context *ctx, const char *reason) { failf(ctx, "%s", reason); } // fail static void warnf(Context *ctx, const char *fmt, ...) ISPRINTF(2,3); static void warnf(Context *ctx, const char *fmt, ...) { if (ctx->out_of_memory) return; va_list ap; va_start(ap, fmt); errorlist_add_va(ctx->warnings, ctx->sourcefile, ctx->sourceline, fmt, ap); va_end(ap); } // warnf static inline void warn(Context *ctx, const char *reason) { warnf(ctx, "%s", reason); } // warn static inline int isfail(const Context *ctx) { return ctx->isfail; } // isfail static void symbolmap_nuke(const void *k, const void *v, void *d) {/*no-op*/} static int create_symbolmap(Context *ctx, SymbolMap *map) { // !!! FIXME: should compare string pointer, with string in cache. map->scope = NULL; map->hash = hash_create(ctx, hash_hash_string, hash_keymatch_string, symbolmap_nuke, 1, MallocBridge, FreeBridge, ctx); return (map->hash != NULL); } // create_symbolmap static void push_symbol(Context *ctx, SymbolMap *map, const char *sym, const MOJOSHADER_astDataType *dt) { // !!! FIXME: decide if this symbol is defined, and if so, if it's in // !!! FIXME: the current scope. SymbolScope *item = (SymbolScope *) Malloc(ctx, sizeof (SymbolScope)); if (item == NULL) return; if (sym != NULL) { if (hash_insert(map->hash, sym, dt) == -1) { Free(ctx, item); return; } // if } // if item->symbol = sym; // cached strings, don't copy. item->datatype = dt; item->next = map->scope; map->scope = item; } // push_symbol static void push_usertype(Context *ctx, const char *sym, const MOJOSHADER_astDataType *dt) { if (sym != NULL) { MOJOSHADER_astDataType *userdt; userdt = (MOJOSHADER_astDataType *) Malloc(ctx, sizeof (*userdt)); if (userdt != NULL) { // !!! FIXME: this is hacky. if (!buffer_append(ctx->garbage, &userdt, sizeof (userdt))) { Free(ctx, userdt); userdt = NULL; } // if userdt->type = MOJOSHADER_AST_DATATYPE_USER; userdt->user.details = dt; userdt->user.name = sym; dt = userdt; } // if } // if push_symbol(ctx, &ctx->usertypes, sym, dt); } // push_usertype static inline void push_variable(Context *ctx, const char *sym, const MOJOSHADER_astDataType *dt) { push_symbol(ctx, &ctx->variables, sym, dt); } // push_variable static inline void push_scope(Context *ctx) { push_usertype(ctx, NULL, NULL); push_variable(ctx, NULL, NULL); } // push_scope static void pop_symbol(Context *ctx, SymbolMap *map) { SymbolScope *item = map->scope; if (!item) return; if (item->symbol) hash_remove(map->hash, item->symbol); map->scope = item->next; Free(ctx, item); } // pop_symbol static void pop_symbol_scope(Context *ctx, SymbolMap *map) { while ((map->scope) && (map->scope->symbol)) pop_symbol(ctx, map); assert(map->scope != NULL); assert(map->scope->symbol == NULL); pop_symbol(ctx, map); } // pop_symbol_scope static inline void pop_scope(Context *ctx) { pop_symbol_scope(ctx, &ctx->usertypes); pop_symbol_scope(ctx, &ctx->variables); } // push_scope static const MOJOSHADER_astDataType *find_symbol(Context *ctx, SymbolMap *map, const char *sym) { const void *value = NULL; hash_find(map->hash, sym, &value); return (const MOJOSHADER_astDataType *) value; } // find_symbol static inline const MOJOSHADER_astDataType *find_usertype(Context *ctx, const char *sym) { return find_symbol(ctx, &ctx->usertypes, sym); } // find_usertype static inline const MOJOSHADER_astDataType *find_variable(Context *ctx, const char *sym) { return find_symbol(ctx, &ctx->variables, sym); } // find_variable static void destroy_symbolmap(Context *ctx, SymbolMap *map) { while (map->scope) pop_symbol(ctx, map); hash_destroy(map->hash); } // destroy_symbolmap static const MOJOSHADER_astDataType *new_datatype_vector(Context *ctx, const MOJOSHADER_astDataType *dt, const int columns) { MOJOSHADER_astDataType *retval; retval = (MOJOSHADER_astDataType *) Malloc(ctx, sizeof (*retval)); if (retval == NULL) return NULL; // !!! FIXME: this is hacky. // !!! FIXME: I'd like to cache these anyhow and reuse types. if (!buffer_append(ctx->garbage, &retval, sizeof (retval))) { Free(ctx, retval); return NULL; } // if if ((columns < 1) || (columns > 4)) fail(ctx, "Vector must have between 1 and 4 elements"); retval->type = MOJOSHADER_AST_DATATYPE_VECTOR; retval->vector.base = dt; retval->vector.elements = columns; return retval; } // new_datatype_vector static const MOJOSHADER_astDataType *new_datatype_matrix(Context *ctx, const MOJOSHADER_astDataType *dt, const int rows, const int columns) { MOJOSHADER_astDataType *retval; // !!! FIXME: allocate enough for a matrix, but we need to cleanup things that copy without checking for subsize. retval = (MOJOSHADER_astDataType *) Malloc(ctx, sizeof (*retval)); if (retval == NULL) return NULL; // !!! FIXME: this is hacky. // !!! FIXME: I'd like to cache these anyhow and reuse types. if (!buffer_append(ctx->garbage, &retval, sizeof (retval))) { Free(ctx, retval); return NULL; } // if if ((rows < 1) || (rows > 4)) fail(ctx, "Matrix must have between 1 and 4 rows"); if ((columns < 1) || (columns > 4)) fail(ctx, "Matrix must have between 1 and 4 columns"); retval->type = MOJOSHADER_AST_DATATYPE_MATRIX; retval->matrix.base = dt; retval->matrix.rows = rows; retval->matrix.columns = columns; return retval; } // new_datatype_matrix // !!! FIXME: move this to mojoshader_ast.c // !!! FIXME: new_* and delete_* should take an allocator, not a context. // These functions are mostly for construction and cleanup of nodes in the // parse tree. Mostly this is simple allocation and initialization, so we // can do as little in the lemon code as possible, and then sort it all out // afterwards. #define NEW_AST_NODE(retval, cls, typ) \ cls *retval = (cls *) Malloc(ctx, sizeof (cls)); \ do { \ if (retval == NULL) { return NULL; } \ retval->ast.type = typ; \ retval->ast.filename = ctx->sourcefile; \ retval->ast.line = ctx->sourceline; \ } while (0) #define DELETE_AST_NODE(cls) do { \ if (!cls) return; \ } while (0) static void delete_compilation_unit(Context*, MOJOSHADER_astCompilationUnit*); static void delete_statement(Context *ctx, MOJOSHADER_astStatement *stmt); static MOJOSHADER_astExpression *new_callfunc_expr(Context *ctx, MOJOSHADER_astExpression *identifier, MOJOSHADER_astArguments *args) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionCallFunction, MOJOSHADER_AST_OP_CALLFUNC); retval->identifier = identifier; retval->args = args; return (MOJOSHADER_astExpression *) retval; } // new_callfunc_expr static MOJOSHADER_astExpression *new_constructor_expr(Context *ctx, const MOJOSHADER_astDataType *dt, MOJOSHADER_astArguments *args) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionConstructor, MOJOSHADER_AST_OP_CONSTRUCTOR); retval->datatype = dt; retval->args = args; return (MOJOSHADER_astExpression *) retval; } // new_constructor_expr static MOJOSHADER_astExpression *new_cast_expr(Context *ctx, const MOJOSHADER_astDataType *dt, MOJOSHADER_astExpression *operand) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionCast, MOJOSHADER_AST_OP_CAST); retval->datatype = dt; retval->operand = operand; return (MOJOSHADER_astExpression *) retval; } // new_cast_expr static MOJOSHADER_astExpression *new_unary_expr(Context *ctx, const MOJOSHADER_astNodeType op, MOJOSHADER_astExpression *operand) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionUnary, op); assert(operator_is_unary(op)); retval->operand = operand; return (MOJOSHADER_astExpression *) retval; } // new_unary_expr static MOJOSHADER_astExpression *new_binary_expr(Context *ctx, const MOJOSHADER_astNodeType op, MOJOSHADER_astExpression *left, MOJOSHADER_astExpression *right) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionBinary, op); assert(operator_is_binary(op)); retval->left = left; retval->right = right; return (MOJOSHADER_astExpression *) retval; } // new_binary_expr static MOJOSHADER_astExpression *new_ternary_expr(Context *ctx, const MOJOSHADER_astNodeType op, MOJOSHADER_astExpression *left, MOJOSHADER_astExpression *center, MOJOSHADER_astExpression *right) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionTernary, op); assert(operator_is_ternary(op)); assert(op == MOJOSHADER_AST_OP_CONDITIONAL); retval->datatype = &ctx->dt_bool; retval->left = left; retval->center = center; retval->right = right; return (MOJOSHADER_astExpression *) retval; } // new_ternary_expr static MOJOSHADER_astExpression *new_deref_struct_expr(Context *ctx, MOJOSHADER_astExpression *identifier, const char *member) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionDerefStruct, MOJOSHADER_AST_OP_DEREF_STRUCT); retval->identifier = identifier; retval->member = member; // cached; don't copy string. retval->isswizzle = 0; // may change during semantic analysis. return (MOJOSHADER_astExpression *) retval; } // new_deref_struct_expr static MOJOSHADER_astExpression *new_identifier_expr(Context *ctx, const char *string) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionIdentifier, MOJOSHADER_AST_OP_IDENTIFIER); retval->identifier = string; // cached; don't copy string. return (MOJOSHADER_astExpression *) retval; } // new_identifier_expr static MOJOSHADER_astExpression *new_literal_int_expr(Context *ctx, const int value) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionIntLiteral, MOJOSHADER_AST_OP_INT_LITERAL); retval->datatype = &ctx->dt_int; retval->value = value; return (MOJOSHADER_astExpression *) retval; } // new_literal_int_expr static MOJOSHADER_astExpression *new_literal_float_expr(Context *ctx, const double dbl) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionFloatLiteral, MOJOSHADER_AST_OP_FLOAT_LITERAL); retval->datatype = &ctx->dt_float; retval->value = dbl; return (MOJOSHADER_astExpression *) retval; } // new_literal_float_expr static MOJOSHADER_astExpression *new_literal_string_expr(Context *ctx, const char *string) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionStringLiteral, MOJOSHADER_AST_OP_STRING_LITERAL); retval->datatype = &ctx->dt_string; retval->string = string; // cached; don't copy string. return (MOJOSHADER_astExpression *) retval; } // new_literal_string_expr static MOJOSHADER_astExpression *new_literal_boolean_expr(Context *ctx, const int value) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionBooleanLiteral, MOJOSHADER_AST_OP_BOOLEAN_LITERAL); retval->datatype = &ctx->dt_bool; retval->value = value; return (MOJOSHADER_astExpression *) retval; } // new_literal_boolean_expr static void delete_arguments(Context *ctx, MOJOSHADER_astArguments *args); static void delete_expr(Context *ctx, MOJOSHADER_astExpression *_expr) { MOJOSHADER_astNode *expr = (MOJOSHADER_astNode *) _expr; DELETE_AST_NODE(expr); if (expr->ast.type == MOJOSHADER_AST_OP_CAST) delete_expr(ctx, expr->cast.operand); else if (expr->ast.type == MOJOSHADER_AST_OP_CONSTRUCTOR) delete_arguments(ctx, expr->constructor.args); else if (expr->ast.type == MOJOSHADER_AST_OP_DEREF_STRUCT) delete_expr(ctx, expr->derefstruct.identifier); else if (operator_is_unary(expr->ast.type)) delete_expr(ctx, expr->unary.operand); else if (operator_is_binary(expr->ast.type)) { delete_expr(ctx, expr->binary.left); delete_expr(ctx, expr->binary.right); } // else if else if (operator_is_ternary(expr->ast.type)) { delete_expr(ctx, expr->ternary.left); delete_expr(ctx, expr->ternary.center); delete_expr(ctx, expr->ternary.right); } // else if else if (expr->ast.type == MOJOSHADER_AST_OP_CALLFUNC) { delete_expr(ctx, expr->callfunc.identifier); delete_arguments(ctx, expr->callfunc.args); } // else if // rest of operators don't have extra data to free. Free(ctx, expr); } // delete_expr static MOJOSHADER_astArguments *new_argument(Context *ctx, MOJOSHADER_astExpression *arg) { NEW_AST_NODE(retval, MOJOSHADER_astArguments, MOJOSHADER_AST_ARGUMENTS); retval->argument = arg; retval->next = NULL; return retval; } // new_argument static void delete_arguments(Context *ctx, MOJOSHADER_astArguments *args) { DELETE_AST_NODE(args); delete_arguments(ctx, args->next); delete_expr(ctx, args->argument); Free(ctx, args); } // delete_arguments static MOJOSHADER_astFunctionParameters *new_function_param(Context *ctx, const MOJOSHADER_astInputModifier inputmod, const MOJOSHADER_astDataType *dt, const char *identifier, const char *semantic, const MOJOSHADER_astInterpolationModifier interpmod, MOJOSHADER_astExpression *initializer) { NEW_AST_NODE(retval, MOJOSHADER_astFunctionParameters, MOJOSHADER_AST_FUNCTION_PARAMS); retval->datatype = dt; retval->input_modifier = inputmod; retval->identifier = identifier; retval->semantic = semantic; retval->interpolation_modifier = interpmod; retval->initializer = initializer; retval->next = NULL; return retval; } // new_function_param static void delete_function_params(Context *ctx, MOJOSHADER_astFunctionParameters *params) { DELETE_AST_NODE(params); delete_function_params(ctx, params->next); delete_expr(ctx, params->initializer); Free(ctx, params); } // delete_function_params static MOJOSHADER_astFunctionSignature *new_function_signature(Context *ctx, const MOJOSHADER_astDataType *dt, const char *identifier, MOJOSHADER_astFunctionParameters *params) { NEW_AST_NODE(retval, MOJOSHADER_astFunctionSignature, MOJOSHADER_AST_FUNCTION_SIGNATURE); retval->datatype = dt; retval->identifier = identifier; retval->params = params; retval->storage_class = MOJOSHADER_AST_FNSTORECLS_NONE; retval->semantic = NULL; return retval; } // new_function_signature static void delete_function_signature(Context *ctx, MOJOSHADER_astFunctionSignature *sig) { DELETE_AST_NODE(sig); delete_function_params(ctx, sig->params); Free(ctx, sig); } // delete_function_signature static MOJOSHADER_astCompilationUnit *new_function(Context *ctx, MOJOSHADER_astFunctionSignature *declaration, MOJOSHADER_astStatement *definition) { NEW_AST_NODE(retval, MOJOSHADER_astCompilationUnitFunction, MOJOSHADER_AST_COMPUNIT_FUNCTION); retval->next = NULL; retval->declaration = declaration; retval->definition = definition; return (MOJOSHADER_astCompilationUnit *) retval; } // new_function static void delete_function(Context *ctx, MOJOSHADER_astCompilationUnitFunction *unitfn) { DELETE_AST_NODE(unitfn); delete_compilation_unit(ctx, unitfn->next); delete_function_signature(ctx, unitfn->declaration); delete_statement(ctx, unitfn->definition); Free(ctx, unitfn); } // delete_function static MOJOSHADER_astScalarOrArray *new_scalar_or_array(Context *ctx, const char *ident, const int isvec, MOJOSHADER_astExpression *dim) { NEW_AST_NODE(retval, MOJOSHADER_astScalarOrArray, MOJOSHADER_AST_SCALAR_OR_ARRAY); retval->identifier = ident; retval->isarray = isvec; retval->dimension = dim; return retval; } // new_scalar_or_array static void delete_scalar_or_array(Context *ctx,MOJOSHADER_astScalarOrArray *s) { DELETE_AST_NODE(s); delete_expr(ctx, s->dimension); Free(ctx, s); } // delete_scalar_or_array static MOJOSHADER_astTypedef *new_typedef(Context *ctx, const int isconst, const MOJOSHADER_astDataType *dt, MOJOSHADER_astScalarOrArray *soa) { // we correct this datatype to the final version during semantic analysis. NEW_AST_NODE(retval, MOJOSHADER_astTypedef, MOJOSHADER_AST_TYPEDEF); retval->datatype = dt; retval->isconst = isconst; retval->details = soa; return retval; } // new_typedef static void delete_typedef(Context *ctx, MOJOSHADER_astTypedef *td) { DELETE_AST_NODE(td); delete_scalar_or_array(ctx, td->details); Free(ctx, td); } // delete_typedef static MOJOSHADER_astPackOffset *new_pack_offset(Context *ctx, const char *a, const char *b) { NEW_AST_NODE(retval, MOJOSHADER_astPackOffset, MOJOSHADER_AST_PACK_OFFSET); retval->ident1 = a; retval->ident2 = b; return retval; } // new_pack_offset static void delete_pack_offset(Context *ctx, MOJOSHADER_astPackOffset *o) { DELETE_AST_NODE(o); Free(ctx, o); } // delete_pack_offset static MOJOSHADER_astVariableLowLevel *new_variable_lowlevel(Context *ctx, MOJOSHADER_astPackOffset *po, const char *reg) { NEW_AST_NODE(retval, MOJOSHADER_astVariableLowLevel, MOJOSHADER_AST_VARIABLE_LOWLEVEL); retval->packoffset = po; retval->register_name = reg; return retval; } // new_variable_lowlevel static void delete_variable_lowlevel(Context *ctx, MOJOSHADER_astVariableLowLevel *vll) { DELETE_AST_NODE(vll); delete_pack_offset(ctx, vll->packoffset); Free(ctx, vll); } // delete_variable_lowlevel static MOJOSHADER_astAnnotations *new_annotation(Context *ctx, const MOJOSHADER_astDataType *dt, MOJOSHADER_astExpression *initializer) { NEW_AST_NODE(retval, MOJOSHADER_astAnnotations, MOJOSHADER_AST_ANNOTATION); retval->datatype = dt; retval->initializer = initializer; retval->next = NULL; return retval; } // new_annotation static void delete_annotation(Context *ctx, MOJOSHADER_astAnnotations *annos) { DELETE_AST_NODE(annos); delete_annotation(ctx, annos->next); delete_expr(ctx, annos->initializer); Free(ctx, annos); } // delete_annotation static MOJOSHADER_astVariableDeclaration *new_variable_declaration( Context *ctx, MOJOSHADER_astScalarOrArray *soa, const char *semantic, MOJOSHADER_astAnnotations *annotations, MOJOSHADER_astExpression *init, MOJOSHADER_astVariableLowLevel *vll) { NEW_AST_NODE(retval, MOJOSHADER_astVariableDeclaration, MOJOSHADER_AST_VARIABLE_DECLARATION); retval->attributes = 0; retval->anonymous_datatype = NULL; retval->details = soa; retval->semantic = semantic; retval->annotations = annotations; retval->initializer = init; retval->lowlevel = vll; retval->next = NULL; return retval; } // new_variable_declaration static void delete_variable_declaration(Context *ctx, MOJOSHADER_astVariableDeclaration *dcl) { DELETE_AST_NODE(dcl); delete_variable_declaration(ctx, dcl->next); delete_scalar_or_array(ctx, dcl->details); delete_annotation(ctx, dcl->annotations); delete_expr(ctx, dcl->initializer); delete_variable_lowlevel(ctx, dcl->lowlevel); Free(ctx, dcl); } // delete_variable_declaration static MOJOSHADER_astCompilationUnit *new_global_variable(Context *ctx, MOJOSHADER_astVariableDeclaration *decl) { NEW_AST_NODE(retval, MOJOSHADER_astCompilationUnitVariable, MOJOSHADER_AST_COMPUNIT_VARIABLE); retval->next = NULL; retval->declaration = decl; return (MOJOSHADER_astCompilationUnit *) retval; } // new_global_variable static void delete_global_variable(Context *ctx, MOJOSHADER_astCompilationUnitVariable *var) { DELETE_AST_NODE(var); delete_compilation_unit(ctx, var->next); delete_variable_declaration(ctx, var->declaration); Free(ctx, var); } // delete_global_variable static MOJOSHADER_astCompilationUnit *new_global_typedef(Context *ctx, MOJOSHADER_astTypedef *td) { NEW_AST_NODE(retval, MOJOSHADER_astCompilationUnitTypedef, MOJOSHADER_AST_COMPUNIT_TYPEDEF); retval->next = NULL; retval->type_info = td; return (MOJOSHADER_astCompilationUnit *) retval; } // new_global_typedef static void delete_global_typedef(Context *ctx, MOJOSHADER_astCompilationUnitTypedef *unit) { DELETE_AST_NODE(unit); delete_compilation_unit(ctx, unit->next); delete_typedef(ctx, unit->type_info); Free(ctx, unit); } // delete_global_typedef static MOJOSHADER_astStructMembers *new_struct_member(Context *ctx, MOJOSHADER_astScalarOrArray *soa, const char *semantic) { NEW_AST_NODE(retval, MOJOSHADER_astStructMembers, MOJOSHADER_AST_STRUCT_MEMBER); retval->semantic = semantic; retval->details = soa; retval->interpolation_mod = MOJOSHADER_AST_INTERPMOD_NONE; retval->next = NULL; return retval; } // new_struct_member static void delete_struct_member(Context *ctx, MOJOSHADER_astStructMembers *member) { DELETE_AST_NODE(member); delete_struct_member(ctx, member->next); delete_scalar_or_array(ctx, member->details); Free(ctx, member); } // delete_struct_member static MOJOSHADER_astStructDeclaration *new_struct_declaration(Context *ctx, const char *name, MOJOSHADER_astStructMembers *members) { NEW_AST_NODE(retval, MOJOSHADER_astStructDeclaration, MOJOSHADER_AST_STRUCT_DECLARATION); retval->datatype = NULL; retval->name = name; retval->members = members; return retval; } // new_struct_declaration static void delete_struct_declaration(Context *ctx, MOJOSHADER_astStructDeclaration *decl) { DELETE_AST_NODE(decl); delete_struct_member(ctx, decl->members); Free(ctx, decl); } // delete_struct_declaration static MOJOSHADER_astCompilationUnit *new_global_struct(Context *ctx, MOJOSHADER_astStructDeclaration *sd) { NEW_AST_NODE(retval, MOJOSHADER_astCompilationUnitStruct, MOJOSHADER_AST_COMPUNIT_STRUCT); retval->next = NULL; retval->struct_info = sd; return (MOJOSHADER_astCompilationUnit *) retval; } // new_global_struct static void delete_global_struct(Context *ctx, MOJOSHADER_astCompilationUnitStruct *unit) { DELETE_AST_NODE(unit); delete_compilation_unit(ctx, unit->next); delete_struct_declaration(ctx, unit->struct_info); Free(ctx, unit); } // delete_global_struct static void delete_compilation_unit(Context *ctx, MOJOSHADER_astCompilationUnit *unit) { if (!unit) return; // it's important to not recurse too deeply here, since you may have // thousands of items in this linked list (each line of a massive // function, for example). To avoid this, we iterate the list here, // deleting all children and making them think they have no reason // to recurse in their own delete methods. // Please note that everyone should _try_ to delete their "next" member, // just in case, but hopefully this cleaned it out. MOJOSHADER_astCompilationUnit *i = unit->next; unit->next = NULL; while (i) { MOJOSHADER_astCompilationUnit *next = i->next; i->next = NULL; delete_compilation_unit(ctx, i); i = next; } // while switch (unit->ast.type) { #define DELETE_UNIT(typ, cls, fn) \ case MOJOSHADER_AST_COMPUNIT_##typ: delete_##fn(ctx, (cls *) unit); break; DELETE_UNIT(FUNCTION, MOJOSHADER_astCompilationUnitFunction, function); DELETE_UNIT(TYPEDEF, MOJOSHADER_astCompilationUnitTypedef, global_typedef); DELETE_UNIT(VARIABLE, MOJOSHADER_astCompilationUnitVariable, global_variable); DELETE_UNIT(STRUCT, MOJOSHADER_astCompilationUnitStruct, global_struct); #undef DELETE_UNIT default: assert(0 && "missing cleanup code"); break; } // switch // don't free (unit) here, the class-specific functions do it. } // delete_compilation_unit static MOJOSHADER_astStatement *new_typedef_statement(Context *ctx, MOJOSHADER_astTypedef *td) { NEW_AST_NODE(retval, MOJOSHADER_astTypedefStatement, MOJOSHADER_AST_STATEMENT_TYPEDEF); retval->next = NULL; retval->type_info = td; return (MOJOSHADER_astStatement *) retval; } // new_typedef_statement static void delete_typedef_statement(Context *ctx, MOJOSHADER_astTypedefStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_typedef(ctx, stmt->type_info); Free(ctx, stmt); } // delete_typedef_statement static MOJOSHADER_astStatement *new_return_statement(Context *ctx, MOJOSHADER_astExpression *expr) { NEW_AST_NODE(retval, MOJOSHADER_astReturnStatement, MOJOSHADER_AST_STATEMENT_RETURN); retval->next = NULL; retval->expr = expr; return (MOJOSHADER_astStatement *) retval; } // new_return_statement static void delete_return_statement(Context *ctx, MOJOSHADER_astReturnStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_expr(ctx, stmt->expr); Free(ctx, stmt); } // delete_return_statement static MOJOSHADER_astStatement *new_block_statement(Context *ctx, MOJOSHADER_astStatement *stmts) { NEW_AST_NODE(retval, MOJOSHADER_astBlockStatement, MOJOSHADER_AST_STATEMENT_BLOCK); retval->next = NULL; retval->statements = stmts; return (MOJOSHADER_astStatement *) retval; } // new_block_statement static void delete_block_statement(Context *ctx, MOJOSHADER_astBlockStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->statements); delete_statement(ctx, stmt->next); Free(ctx, stmt); } // delete_statement_block static MOJOSHADER_astStatement *new_for_statement(Context *ctx, MOJOSHADER_astVariableDeclaration *decl, MOJOSHADER_astExpression *initializer, MOJOSHADER_astExpression *looptest, MOJOSHADER_astExpression *counter, MOJOSHADER_astStatement *statement) { NEW_AST_NODE(retval, MOJOSHADER_astForStatement, MOJOSHADER_AST_STATEMENT_FOR); retval->next = NULL; retval->unroll = -1; retval->var_decl = decl; retval->initializer = initializer; retval->looptest = looptest; retval->counter = counter; retval->statement = statement; return (MOJOSHADER_astStatement *) retval; } // new_for_statement static void delete_for_statement(Context *ctx,MOJOSHADER_astForStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_variable_declaration(ctx, stmt->var_decl); delete_expr(ctx, stmt->initializer); delete_expr(ctx, stmt->looptest); delete_expr(ctx, stmt->counter); delete_statement(ctx, stmt->statement); Free(ctx, stmt); } // delete_for_statement static MOJOSHADER_astStatement *new_do_statement(Context *ctx, const int unroll, MOJOSHADER_astStatement *stmt, MOJOSHADER_astExpression *expr) { NEW_AST_NODE(retval,MOJOSHADER_astDoStatement,MOJOSHADER_AST_STATEMENT_DO); retval->next = NULL; retval->unroll = unroll; retval->expr = expr; retval->statement = stmt; return (MOJOSHADER_astStatement *) retval; } // new_do_statement static void delete_do_statement(Context *ctx, MOJOSHADER_astDoStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_statement(ctx, stmt->statement); delete_expr(ctx, stmt->expr); Free(ctx, stmt); } // delete_do_statement static MOJOSHADER_astStatement *new_while_statement(Context *ctx, const int unroll, MOJOSHADER_astExpression *expr, MOJOSHADER_astStatement *stmt) { NEW_AST_NODE(retval, MOJOSHADER_astWhileStatement, MOJOSHADER_AST_STATEMENT_WHILE); retval->next = NULL; retval->unroll = unroll; retval->expr = expr; retval->statement = stmt; return (MOJOSHADER_astStatement *) retval; } // new_while_statement static void delete_while_statement(Context *ctx, MOJOSHADER_astWhileStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_statement(ctx, stmt->statement); delete_expr(ctx, stmt->expr); Free(ctx, stmt); } // delete_while_statement static MOJOSHADER_astStatement *new_if_statement(Context *ctx, const int attr, MOJOSHADER_astExpression *expr, MOJOSHADER_astStatement *stmt, MOJOSHADER_astStatement *elsestmt) { NEW_AST_NODE(retval,MOJOSHADER_astIfStatement,MOJOSHADER_AST_STATEMENT_IF); retval->next = NULL; retval->attributes = attr; retval->expr = expr; retval->statement = stmt; retval->else_statement = elsestmt; return (MOJOSHADER_astStatement *) retval; } // new_if_statement static void delete_if_statement(Context *ctx, MOJOSHADER_astIfStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_expr(ctx, stmt->expr); delete_statement(ctx, stmt->statement); delete_statement(ctx, stmt->else_statement); Free(ctx, stmt); } // delete_if_statement static MOJOSHADER_astSwitchCases *new_switch_case(Context *ctx, MOJOSHADER_astExpression *expr, MOJOSHADER_astStatement *stmt) { NEW_AST_NODE(retval, MOJOSHADER_astSwitchCases, MOJOSHADER_AST_SWITCH_CASE); retval->expr = expr; retval->statement = stmt; retval->next = NULL; return retval; } // new_switch_case static void delete_switch_case(Context *ctx, MOJOSHADER_astSwitchCases *sc) { DELETE_AST_NODE(sc); delete_switch_case(ctx, sc->next); delete_expr(ctx, sc->expr); delete_statement(ctx, sc->statement); Free(ctx, sc); } // delete_switch_case static MOJOSHADER_astStatement *new_empty_statement(Context *ctx) { NEW_AST_NODE(retval, MOJOSHADER_astEmptyStatement, MOJOSHADER_AST_STATEMENT_EMPTY); retval->next = NULL; return (MOJOSHADER_astStatement *) retval; } // new_empty_statement static void delete_empty_statement(Context *ctx, MOJOSHADER_astEmptyStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); Free(ctx, stmt); } // delete_empty_statement static MOJOSHADER_astStatement *new_break_statement(Context *ctx) { NEW_AST_NODE(retval, MOJOSHADER_astBreakStatement, MOJOSHADER_AST_STATEMENT_BREAK); retval->next = NULL; return (MOJOSHADER_astStatement *) retval; } // new_break_statement static void delete_break_statement(Context *ctx, MOJOSHADER_astBreakStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); Free(ctx, stmt); } // delete_break_statement static MOJOSHADER_astStatement *new_continue_statement(Context *ctx) { NEW_AST_NODE(retval, MOJOSHADER_astContinueStatement, MOJOSHADER_AST_STATEMENT_CONTINUE); retval->next = NULL; return (MOJOSHADER_astStatement *) retval; } // new_continue_statement static void delete_continue_statement(Context *ctx, MOJOSHADER_astContinueStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); Free(ctx, stmt); } // delete_continue_statement static MOJOSHADER_astStatement *new_discard_statement(Context *ctx) { NEW_AST_NODE(retval, MOJOSHADER_astDiscardStatement, MOJOSHADER_AST_STATEMENT_DISCARD); retval->next = NULL; return (MOJOSHADER_astStatement *) retval; } // new_discard_statement static void delete_discard_statement(Context *ctx, MOJOSHADER_astDiscardStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); Free(ctx, stmt); } // delete_discard_statement static MOJOSHADER_astStatement *new_expr_statement(Context *ctx, MOJOSHADER_astExpression *expr) { NEW_AST_NODE(retval, MOJOSHADER_astExpressionStatement, MOJOSHADER_AST_STATEMENT_EXPRESSION); retval->next = NULL; retval->expr = expr; return (MOJOSHADER_astStatement *) retval; } // new_expr_statement static void delete_expr_statement(Context *ctx, MOJOSHADER_astExpressionStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_expr(ctx, stmt->expr); Free(ctx, stmt); } // delete_expr_statement static MOJOSHADER_astStatement *new_switch_statement(Context *ctx, const int attr, MOJOSHADER_astExpression *expr, MOJOSHADER_astSwitchCases *cases) { NEW_AST_NODE(retval, MOJOSHADER_astSwitchStatement, MOJOSHADER_AST_STATEMENT_SWITCH); retval->next = NULL; retval->attributes = attr; retval->expr = expr; retval->cases = cases; return (MOJOSHADER_astStatement *) retval; } // new_switch_statement static void delete_switch_statement(Context *ctx, MOJOSHADER_astSwitchStatement *stmt) { DELETE_AST_NODE(stmt); delete_expr(ctx, stmt->expr); delete_switch_case(ctx, stmt->cases); Free(ctx, stmt); } // delete_switch_statement static MOJOSHADER_astStatement *new_struct_statement(Context *ctx, MOJOSHADER_astStructDeclaration *sd) { NEW_AST_NODE(retval, MOJOSHADER_astStructStatement, MOJOSHADER_AST_STATEMENT_STRUCT); retval->next = NULL; retval->struct_info = sd; return (MOJOSHADER_astStatement *) retval; } // new_struct_statement static void delete_struct_statement(Context *ctx, MOJOSHADER_astStructStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_struct_declaration(ctx, stmt->struct_info); Free(ctx, stmt); } // delete_struct_statement static MOJOSHADER_astStatement *new_vardecl_statement(Context *ctx, MOJOSHADER_astVariableDeclaration *vd) { NEW_AST_NODE(retval, MOJOSHADER_astVarDeclStatement, MOJOSHADER_AST_STATEMENT_VARDECL); retval->next = NULL; retval->declaration = vd; return (MOJOSHADER_astStatement *) retval; } // new_vardecl_statement static void delete_vardecl_statement(Context *ctx, MOJOSHADER_astVarDeclStatement *stmt) { DELETE_AST_NODE(stmt); delete_statement(ctx, stmt->next); delete_variable_declaration(ctx, stmt->declaration); Free(ctx, stmt); } // delete_vardecl_statement static void delete_statement(Context *ctx, MOJOSHADER_astStatement *stmt) { if (!stmt) return; // it's important to not recurse too deeply here, since you may have // thousands of items in this linked list (each line of a massive // function, for example). To avoid this, we iterate the list here, // deleting all children and making them think they have no reason // to recurse in their own delete methods. // Please note that everyone should _try_ to delete their "next" member, // just in case, but hopefully this cleaned it out. MOJOSHADER_astStatement *i = stmt->next; stmt->next = NULL; while (i) { MOJOSHADER_astStatement *next = i->next; i->next = NULL; delete_statement(ctx, i); i = next; } // while switch (stmt->ast.type) { #define DELETE_STATEMENT(typ, cls, fn) \ case MOJOSHADER_AST_STATEMENT_##typ: \ delete_##fn##_statement(ctx, (cls *) stmt); break; DELETE_STATEMENT(BLOCK, MOJOSHADER_astBlockStatement, block); DELETE_STATEMENT(EMPTY, MOJOSHADER_astEmptyStatement, empty); DELETE_STATEMENT(IF, MOJOSHADER_astIfStatement, if); DELETE_STATEMENT(SWITCH, MOJOSHADER_astSwitchStatement, switch); DELETE_STATEMENT(EXPRESSION, MOJOSHADER_astExpressionStatement, expr); DELETE_STATEMENT(FOR, MOJOSHADER_astForStatement, for); DELETE_STATEMENT(DO, MOJOSHADER_astDoStatement, do); DELETE_STATEMENT(WHILE, MOJOSHADER_astWhileStatement, while); DELETE_STATEMENT(RETURN, MOJOSHADER_astReturnStatement, return); DELETE_STATEMENT(BREAK, MOJOSHADER_astBreakStatement, break); DELETE_STATEMENT(CONTINUE, MOJOSHADER_astContinueStatement, continue); DELETE_STATEMENT(DISCARD, MOJOSHADER_astDiscardStatement, discard); DELETE_STATEMENT(TYPEDEF, MOJOSHADER_astTypedefStatement, typedef); DELETE_STATEMENT(STRUCT, MOJOSHADER_astStructStatement, struct); DELETE_STATEMENT(VARDECL, MOJOSHADER_astVarDeclStatement, vardecl); #undef DELETE_STATEMENT default: assert(0 && "missing cleanup code"); break; } // switch // don't free (stmt) here, the class-specific functions do it. } // delete_statement static const MOJOSHADER_astDataType *get_usertype(const Context *ctx, const char *token) { const void *value; // search all scopes. if (!hash_find(ctx->usertypes.hash, token, &value)) return NULL; return (MOJOSHADER_astDataType *) value; } // get_usertype // This is where the actual parsing happens. It's Lemon-generated! #define __MOJOSHADER_HLSL_COMPILER__ 1 #include "mojoshader_parser_hlsl.h" #if 0 static int expr_is_constant(MOJOSHADER_astExpression *expr) { const MOJOSHADER_astNodeType op = expr->ast.type; if (operator_is_unary(op)) return expr_is_constant(expr->unary.operand); else if (operator_is_binary(op)) { return ( expr_is_constant(expr->binary.left) && expr_is_constant(expr->binary.right) ); } // else if else if (operator_is_ternary(op)) { return ( expr_is_constant(expr->ternary.left) && expr_is_constant(expr->ternary.center) && expr_is_constant(expr->ternary.right) ); } // else if return ( (op == MOJOSHADER_AST_OP_INT_LITERAL) || (op == MOJOSHADER_AST_OP_FLOAT_LITERAL) || (op == MOJOSHADER_AST_OP_STRING_LITERAL) || (op == MOJOSHADER_AST_OP_BOOLEAN_LITERAL) ); } // expr_is_constant #endif typedef struct AstCalcData { int isflt; union { double f; int64 i; } value; } AstCalcData; // returns 0 if this expression is non-constant, 1 if it is. // calculation results land in (data). static int calc_ast_const_expr(Context *ctx, void *_expr, AstCalcData *data) { const MOJOSHADER_astNode *expr = (MOJOSHADER_astNode *) _expr; const MOJOSHADER_astNodeType op = expr->ast.type; ctx->sourcefile = expr->ast.filename; ctx->sourceline = expr->ast.line; if (operator_is_unary(op)) { if (!calc_ast_const_expr(ctx, expr->unary.operand, data)) return 0; if (data->isflt) { switch (op) { case MOJOSHADER_AST_OP_NEGATE: data->value.f = -data->value.f; return 1; case MOJOSHADER_AST_OP_NOT: data->value.f = !data->value.f; return 1; case MOJOSHADER_AST_OP_COMPLEMENT: fail(ctx, "integer operation on floating point value"); return 0; case MOJOSHADER_AST_OP_CAST: // !!! FIXME: this should work, but it's complicated. assert(0 && "write me"); return 0; default: break; } // switch } // if else // integer version { switch (op) { case MOJOSHADER_AST_OP_NEGATE: data->value.i = -data->value.i; return 1; case MOJOSHADER_AST_OP_NOT: data->value.i = !data->value.i; return 1; case MOJOSHADER_AST_OP_COMPLEMENT: data->value.i = ~data->value.i; return 1; case MOJOSHADER_AST_OP_CAST: // !!! FIXME: this should work, but it's complicated. assert(0 && "write me"); return 0; default: break; } // switch } // else assert(0 && "unhandled operation?"); return 0; } // if else if (operator_is_binary(op)) { AstCalcData subdata2; if ( (!calc_ast_const_expr(ctx, expr->binary.left, data)) || (!calc_ast_const_expr(ctx, expr->binary.right, &subdata2)) ) return 0; // upgrade to float if either operand is float. if ((data->isflt) || (subdata2.isflt)) { if (!data->isflt) data->value.f = (double) data->value.i; if (!subdata2.isflt) subdata2.value.f = (double) subdata2.value.i; data->isflt = subdata2.isflt = 1; } // if switch (op) { // gcc doesn't handle commas here, either (fails to parse!). case MOJOSHADER_AST_OP_COMMA: case MOJOSHADER_AST_OP_ASSIGN: case MOJOSHADER_AST_OP_MULASSIGN: case MOJOSHADER_AST_OP_DIVASSIGN: case MOJOSHADER_AST_OP_MODASSIGN: case MOJOSHADER_AST_OP_ADDASSIGN: case MOJOSHADER_AST_OP_SUBASSIGN: case MOJOSHADER_AST_OP_LSHIFTASSIGN: case MOJOSHADER_AST_OP_RSHIFTASSIGN: case MOJOSHADER_AST_OP_ANDASSIGN: case MOJOSHADER_AST_OP_XORASSIGN: case MOJOSHADER_AST_OP_ORASSIGN: return 0; // assignment is non-constant. default: break; } // switch if (data->isflt) { switch (op) { case MOJOSHADER_AST_OP_MULTIPLY: data->value.f *= subdata2.value.f; return 1; case MOJOSHADER_AST_OP_DIVIDE: data->value.f /= subdata2.value.f; return 1; case MOJOSHADER_AST_OP_ADD: data->value.f += subdata2.value.f; return 1; case MOJOSHADER_AST_OP_SUBTRACT: data->value.f -= subdata2.value.f; return 1; case MOJOSHADER_AST_OP_LESSTHAN: data->isflt = 0; data->value.i = data->value.f < subdata2.value.f; return 1; case MOJOSHADER_AST_OP_GREATERTHAN: data->isflt = 0; data->value.i = data->value.f > subdata2.value.f; return 1; case MOJOSHADER_AST_OP_LESSTHANOREQUAL: data->isflt = 0; data->value.i = data->value.f <= subdata2.value.f; return 1; case MOJOSHADER_AST_OP_GREATERTHANOREQUAL: data->isflt = 0; data->value.i = data->value.f >= subdata2.value.f; return 1; case MOJOSHADER_AST_OP_EQUAL: data->isflt = 0; data->value.i = data->value.f == subdata2.value.f; return 1; case MOJOSHADER_AST_OP_NOTEQUAL: data->isflt = 0; data->value.i = data->value.f != subdata2.value.f; return 1; case MOJOSHADER_AST_OP_LOGICALAND: data->isflt = 0; data->value.i = data->value.f && subdata2.value.f; return 1; case MOJOSHADER_AST_OP_LOGICALOR: data->isflt = 0; data->value.i = data->value.f || subdata2.value.f; return 1; case MOJOSHADER_AST_OP_LSHIFT: case MOJOSHADER_AST_OP_RSHIFT: case MOJOSHADER_AST_OP_MODULO: case MOJOSHADER_AST_OP_BINARYAND: case MOJOSHADER_AST_OP_BINARYXOR: case MOJOSHADER_AST_OP_BINARYOR: fail(ctx, "integer operation on floating point value"); return 0; default: break; } // switch } // if else // integer version. { switch (op) { case MOJOSHADER_AST_OP_MULTIPLY: data->value.i *= subdata2.value.i; return 1; case MOJOSHADER_AST_OP_DIVIDE: data->value.i /= subdata2.value.i; return 1; case MOJOSHADER_AST_OP_ADD: data->value.i += subdata2.value.i; return 1; case MOJOSHADER_AST_OP_SUBTRACT: data->value.i -= subdata2.value.i; return 1; case MOJOSHADER_AST_OP_LESSTHAN: data->value.i = data->value.i < subdata2.value.i; return 1; case MOJOSHADER_AST_OP_GREATERTHAN: data->value.i = data->value.i > subdata2.value.i; return 1; case MOJOSHADER_AST_OP_LESSTHANOREQUAL: data->value.i = data->value.i <= subdata2.value.i; return 1; case MOJOSHADER_AST_OP_GREATERTHANOREQUAL: data->value.i = data->value.i >= subdata2.value.i; return 1; case MOJOSHADER_AST_OP_EQUAL: data->value.i = data->value.i == subdata2.value.i; return 1; case MOJOSHADER_AST_OP_NOTEQUAL: data->value.i = data->value.i != subdata2.value.i; return 1; case MOJOSHADER_AST_OP_LOGICALAND: data->value.i = data->value.i && subdata2.value.i; return 1; case MOJOSHADER_AST_OP_LOGICALOR: data->value.i = data->value.i || subdata2.value.i; return 1; case MOJOSHADER_AST_OP_LSHIFT: data->value.i = data->value.i << subdata2.value.i; return 1; case MOJOSHADER_AST_OP_RSHIFT: data->value.i = data->value.i >> subdata2.value.i; return 1; case MOJOSHADER_AST_OP_MODULO: data->value.i = data->value.i % subdata2.value.i; return 1; case MOJOSHADER_AST_OP_BINARYAND: data->value.i = data->value.i & subdata2.value.i; return 1; case MOJOSHADER_AST_OP_BINARYXOR: data->value.i = data->value.i ^ subdata2.value.i; return 1; case MOJOSHADER_AST_OP_BINARYOR: data->value.i = data->value.i | subdata2.value.i; return 1; default: break; } // switch } // else assert(0 && "unhandled operation?"); return 0; } // else if else if (operator_is_ternary(op)) { AstCalcData subdata2; AstCalcData subdata3; assert(op == MOJOSHADER_AST_OP_CONDITIONAL); // only one we have. if ( (!calc_ast_const_expr(ctx, expr->ternary.left, data)) || (!calc_ast_const_expr(ctx, expr->ternary.center, &subdata2)) || (!calc_ast_const_expr(ctx, expr->ternary.right, &subdata3)) ) return 0; // first operand should be bool (for the one ternary operator we have). if (data->isflt) { data->isflt = 0; data->value.i = (int64) subdata3.value.f; } // if // upgrade to float if either operand is float. if ((subdata2.isflt) || (subdata3.isflt)) { if (!subdata2.isflt) subdata2.value.f = (double) subdata2.value.i; if (!subdata3.isflt) subdata3.value.f = (double) subdata3.value.i; subdata2.isflt = subdata3.isflt = 1; } // if data->isflt = subdata2.isflt; if (data->isflt) data->value.f = data->value.i ? subdata2.value.f : subdata3.value.f; else data->value.i = data->value.i ? subdata2.value.i : subdata3.value.i; return 1; } // else if else // not an operator? See if this is a literal value. { switch (op) { case MOJOSHADER_AST_OP_INT_LITERAL: data->isflt = 0; data->value.i = expr->intliteral.value; return 1; case MOJOSHADER_AST_OP_FLOAT_LITERAL: data->isflt = 1; data->value.f = expr->floatliteral.value; return 1; case MOJOSHADER_AST_OP_BOOLEAN_LITERAL: data->isflt = 0; data->value.i = expr->boolliteral.value ? 1 : 0; return 1; default: break; } // switch } // switch return 0; // not constant, or unhandled. } // calc_ast_const_expr static inline const MOJOSHADER_astDataType *reduce_datatype(const MOJOSHADER_astDataType *dt) { const MOJOSHADER_astDataType *retval = dt; while (retval && retval->type == MOJOSHADER_AST_DATATYPE_USER) retval = retval->user.details; return retval; } // reduce_datatype static const MOJOSHADER_astDataType *build_datatype(Context *ctx, const int isconst, const MOJOSHADER_astDataType *dt, MOJOSHADER_astScalarOrArray *soa) { MOJOSHADER_astDataType *retval = NULL; assert( (soa->isarray && soa->dimension) || (!soa->isarray && !soa->dimension) ); // see if we can just reuse the exist datatype. if (!soa->isarray) { const int c1 = (dt->type & MOJOSHADER_AST_DATATYPE_CONST) != 0; const int c2 = (isconst != 0); if (c1 == c2) return dt; // reuse existing datatype! } // if retval = (MOJOSHADER_astDataType *) Malloc(ctx, sizeof (*retval)); if (retval == NULL) return NULL; // !!! FIXME: this is hacky. if (!buffer_append(ctx->garbage, &retval, sizeof (retval))) { Free(ctx, retval); return NULL; } // if if (!soa->isarray) { assert(soa->dimension == NULL); memcpy(retval, dt, sizeof (MOJOSHADER_astDataType)); if (isconst) retval->type |= MOJOSHADER_AST_DATATYPE_CONST; else retval->type &= ~MOJOSHADER_AST_DATATYPE_CONST; return retval; } // if retval->type = MOJOSHADER_AST_DATATYPE_ARRAY; retval->array.base = dt; if (soa->dimension == NULL) { retval->array.elements = -1; return retval; } // if // Run the expression to verify it's constant and produces a positive int. AstCalcData data; data.isflt = 0; data.value.i = 0; retval->array.elements = 16; // sane default for failure. const int ok = calc_ast_const_expr(ctx, soa->dimension, &data); // reset error position. ctx->sourcefile = soa->ast.filename; ctx->sourceline = soa->ast.line; if (!ok) fail(ctx, "array dimensions not constant"); else if (data.isflt) fail(ctx, "array dimensions not integer"); else if (data.value.i < 0) fail(ctx, "array dimensions negative"); else retval->array.elements = data.value.i; return retval; } // build_datatype static void require_numeric_datatype(Context *ctx, const MOJOSHADER_astDataType *datatype) { datatype = reduce_datatype(datatype); switch (datatype->type) { case MOJOSHADER_AST_DATATYPE_BOOL: case MOJOSHADER_AST_DATATYPE_INT: case MOJOSHADER_AST_DATATYPE_UINT: case MOJOSHADER_AST_DATATYPE_HALF: case MOJOSHADER_AST_DATATYPE_FLOAT: case MOJOSHADER_AST_DATATYPE_DOUBLE: return; default: break; } // switch fail(ctx, "Expected numeric type"); // !!! FIXME: fmt. // !!! FIXME: replace AST node with an AST_OP_INT_LITERAL zero, keep going. } // require_numeric_datatype static void require_integer_datatype(Context *ctx, const MOJOSHADER_astDataType *datatype) { datatype = reduce_datatype(datatype); switch (datatype->type) { case MOJOSHADER_AST_DATATYPE_INT: case MOJOSHADER_AST_DATATYPE_UINT: return; default: break; } // switch fail(ctx, "Expected integer type"); // !!! FIXME: fmt. // !!! FIXME: replace AST node with an AST_OP_INT_LITERAL zero, keep going. } // require_integer_datatype static void require_boolean_datatype(Context *ctx, const MOJOSHADER_astDataType *datatype) { datatype = reduce_datatype(datatype); switch (datatype->type) { case MOJOSHADER_AST_DATATYPE_BOOL: case MOJOSHADER_AST_DATATYPE_INT: case MOJOSHADER_AST_DATATYPE_UINT: return; default: break; } // switch fail(ctx, "Expected boolean type"); // !!! FIXME: fmt. // !!! FIXME: replace AST node with an AST_OP_BOOLEAN_LITERAL false, keep going. } // require_numeric_datatype static void require_array_datatype(Context *ctx, const MOJOSHADER_astDataType *datatype) { datatype = reduce_datatype(datatype); if (datatype->type == MOJOSHADER_AST_DATATYPE_ARRAY) return; fail(ctx, "expected array"); // !!! FIXME: delete array dereference for further processing. } // require_array_datatype static void require_struct_datatype(Context *ctx, const MOJOSHADER_astDataType *datatype) { datatype = reduce_datatype(datatype); if (datatype->type == MOJOSHADER_AST_DATATYPE_STRUCT) return; fail(ctx, "expected struct"); // !!! FIXME: delete struct dereference for further processing. } // require_struct_datatype static const MOJOSHADER_astDataType *require_function_datatype(Context *ctx, const MOJOSHADER_astDataType *datatype) { datatype = reduce_datatype(datatype); if (datatype->type != MOJOSHADER_AST_DATATYPE_FUNCTION) { fail(ctx, "expected function"); // !!! FIXME: delete function call for further processing. return &ctx->dt_int; } // if return datatype->function.retval; } // require_function_datatype // Extract the individual element type from an array datatype. static const MOJOSHADER_astDataType *array_element_datatype(Context *ctx, const MOJOSHADER_astDataType *datatype) { datatype = reduce_datatype(datatype); assert(datatype->type == MOJOSHADER_AST_DATATYPE_ARRAY); return datatype->array.base; } // array_element_datatype // This tests two datatypes to see if they are compatible, and adds cast // operator nodes to the AST if the program was relying on implicit // casts between then. Will fail() if the datatypes can't be coerced // with a cast at all. (left) can be NULL to say that its datatype is // set in stone (an lvalue, for example). No other NULLs are allowed. // Returns final datatype used once implicit casting is complete. // The datatypes must be pointers from the string cache. static const MOJOSHADER_astDataType *add_type_coercion(Context *ctx, MOJOSHADER_astExpression **left, const MOJOSHADER_astDataType *_ldatatype, MOJOSHADER_astExpression **right, const MOJOSHADER_astDataType *_rdatatype) { // !!! FIXME: this whole function is probably naive at best. const MOJOSHADER_astDataType *ldatatype = reduce_datatype(_ldatatype); const MOJOSHADER_astDataType *rdatatype = reduce_datatype(_rdatatype); if (ldatatype == rdatatype) return ldatatype; // they already match, so we're done. struct { const MOJOSHADER_astDataTypeType type; const int bits; const int is_unsigned; const int floating; } typeinf[] = { { MOJOSHADER_AST_DATATYPE_BOOL, 1, 1, 0 }, { MOJOSHADER_AST_DATATYPE_HALF, 16, 0, 1 }, { MOJOSHADER_AST_DATATYPE_INT, 32, 0, 0 }, { MOJOSHADER_AST_DATATYPE_UINT, 32, 1, 0 }, { MOJOSHADER_AST_DATATYPE_FLOAT, 32, 0, 1 }, { MOJOSHADER_AST_DATATYPE_DOUBLE, 64, 0, 1 }, }; int l = STATICARRAYLEN(typeinf); if (ldatatype != NULL) { for (l = 0; l < STATICARRAYLEN(typeinf); l++) { if (typeinf[l].type == ldatatype->type) break; } // for } // if int r = STATICARRAYLEN(typeinf); if (rdatatype != NULL) { for (r = 0; r < STATICARRAYLEN(typeinf); r++) { if (typeinf[r].type == rdatatype->type) break; } // for } // if enum { CHOOSE_NEITHER, CHOOSE_LEFT, CHOOSE_RIGHT } choice = CHOOSE_NEITHER; if ((l < STATICARRAYLEN(typeinf)) && (r < STATICARRAYLEN(typeinf))) { if (left == NULL) choice = CHOOSE_LEFT; // we need to force to the lvalue. else if (typeinf[l].bits > typeinf[r].bits) choice = CHOOSE_LEFT; else if (typeinf[l].bits < typeinf[r].bits) choice = CHOOSE_RIGHT; else if (typeinf[l].floating && !typeinf[r].floating) choice = CHOOSE_LEFT; else if (!typeinf[l].floating && typeinf[r].floating) choice = CHOOSE_RIGHT; else if (typeinf[l].is_unsigned && !typeinf[r].is_unsigned) choice = CHOOSE_LEFT; else if (!typeinf[l].is_unsigned && typeinf[r].is_unsigned) choice = CHOOSE_RIGHT; } // if if (choice == CHOOSE_LEFT) { *right = new_cast_expr(ctx, _ldatatype, *right); return _ldatatype; } // if else if (choice == CHOOSE_RIGHT) { *left = new_cast_expr(ctx, _rdatatype, *left); return _rdatatype; } // else if assert(choice == CHOOSE_NEITHER); fail(ctx, "incompatible data types"); // Ditch original (*right), force a literal value that matches // ldatatype, so further processing is normalized. // !!! FIXME: force (right) to match (left). delete_expr(ctx, *right); *right = new_cast_expr(ctx, _ldatatype, new_literal_int_expr(ctx, 0)); return ldatatype; } // add_type_coercion static int is_swizzle_str(const char *str) { int i; int is_xyzw = 0; int is_rgba = 0; assert(*str != '\0'); // can this actually happen? for (i = 0; i < 4; i++, str++) { const char ch = *str; if (ch == '\0') break; else if ((ch == 'x') || (ch == 'y') || (ch == 'z') || (ch == 'w')) is_xyzw = 1; else if ((ch == 'r') || (ch == 'g') || (ch == 'b') || (ch == 'a')) is_rgba = 1; } // for if (*str != '\0') // must be end of string here. return 0; // not a swizzle. return ((is_rgba + is_xyzw) == 1); // can only be one or the other. } // is_swizzle_str // Go through the AST and make sure all datatypes check out okay. For datatypes // that are compatible but are relying on an implicit cast, we add explicit // casts to the AST here, so further processing doesn't have to worry about // type coercion. // For things that are incompatible, we generate errors and // then replace them with reasonable defaults so further processing can // continue (but code generation will be skipped due to errors). // This means further processing can assume the AST is sane and not have to // spend effort verifying it again. // This stage will also set every AST node's datatype field, if it is // meaningful to do so. This will allow conversion to IR to know what // type/size a given node is. static const MOJOSHADER_astDataType *type_check_ast(Context *ctx, void *_ast) { MOJOSHADER_astNode *ast = (MOJOSHADER_astNode *) _ast; const MOJOSHADER_astDataType *datatype = NULL; const MOJOSHADER_astDataType *datatype2 = NULL; const MOJOSHADER_astDataType *datatype3 = NULL; if ((!ast) || (ctx->out_of_memory)) return NULL; // upkeep so we report correct error locations... ctx->sourcefile = ast->ast.filename; ctx->sourceline = ast->ast.line; switch (ast->ast.type) { case MOJOSHADER_AST_OP_POSTINCREMENT: case MOJOSHADER_AST_OP_POSTDECREMENT: case MOJOSHADER_AST_OP_PREINCREMENT: case MOJOSHADER_AST_OP_PREDECREMENT: case MOJOSHADER_AST_OP_COMPLEMENT: case MOJOSHADER_AST_OP_NEGATE: case MOJOSHADER_AST_OP_NOT: datatype = type_check_ast(ctx, ast->unary.operand); require_numeric_datatype(ctx, datatype); ast->unary.datatype = datatype; return datatype; case MOJOSHADER_AST_OP_DEREF_ARRAY: datatype = type_check_ast(ctx, ast->binary.left); datatype2 = type_check_ast(ctx, ast->binary.right); require_array_datatype(ctx, datatype); require_integer_datatype(ctx, datatype2); add_type_coercion(ctx, NULL, &ctx->dt_int, &ast->binary.right, datatype2); ast->binary.datatype = array_element_datatype(ctx, datatype); return ast->binary.datatype; case MOJOSHADER_AST_OP_DEREF_STRUCT: { const char *member = ast->derefstruct.member; datatype = type_check_ast(ctx, ast->derefstruct.identifier); const MOJOSHADER_astDataType *reduced = reduce_datatype(datatype); // Is this a swizzle and not a struct deref? if (reduced->type == MOJOSHADER_AST_DATATYPE_VECTOR) { ast->derefstruct.isswizzle = 1; if (!is_swizzle_str(member)) { fail(ctx, "invalid swizzle on vector"); // force this to be sane for further processing. const char *sane_swiz = stringcache(ctx->strcache, "xyzw"); ast->derefstruct.member = sane_swiz; } // if ast->derefstruct.datatype = datatype; // !!! FIXME: should float4(0,0,0,0).xy become a float2? return ast->derefstruct.datatype; } // if // maybe this is an actual struct? // !!! FIXME: replace with an int or something if not. require_struct_datatype(ctx, reduced); // map member to datatype assert(ast->derefstruct.datatype == NULL); const MOJOSHADER_astDataTypeStructMember *mbrs = datatype->structure.members; int i; for (i = 0; i < reduced->structure.member_count; i++) { if (strcmp(mbrs[i].identifier, member) == 0) { ast->derefstruct.datatype = mbrs[i].datatype; break; } // if } // for if (ast->derefstruct.datatype == NULL) { // !!! FIXME: replace with an int or something. failf(ctx, "Struct has no member named '%s'", member); } // if return ast->derefstruct.datatype; } // case case MOJOSHADER_AST_OP_COMMA: // evaluate and throw away left, return right. type_check_ast(ctx, ast->binary.left); ast->binary.datatype = type_check_ast(ctx, ast->binary.right); return ast->binary.datatype; case MOJOSHADER_AST_OP_MULTIPLY: case MOJOSHADER_AST_OP_DIVIDE: case MOJOSHADER_AST_OP_ADD: case MOJOSHADER_AST_OP_SUBTRACT: datatype = type_check_ast(ctx, ast->binary.left); datatype2 = type_check_ast(ctx, ast->binary.right); require_numeric_datatype(ctx, datatype); require_numeric_datatype(ctx, datatype2); ast->binary.datatype = add_type_coercion(ctx, &ast->binary.left, datatype, &ast->binary.right, datatype2); return ast->binary.datatype; case MOJOSHADER_AST_OP_LSHIFT: case MOJOSHADER_AST_OP_RSHIFT: case MOJOSHADER_AST_OP_MODULO: datatype = type_check_ast(ctx, ast->binary.left); datatype2 = type_check_ast(ctx, ast->binary.right); require_integer_datatype(ctx, datatype); require_integer_datatype(ctx, datatype2); ast->binary.datatype = add_type_coercion(ctx, &ast->binary.left, datatype, &ast->binary.right, datatype2); return ast->binary.datatype; case MOJOSHADER_AST_OP_LESSTHAN: case MOJOSHADER_AST_OP_GREATERTHAN: case MOJOSHADER_AST_OP_LESSTHANOREQUAL: case MOJOSHADER_AST_OP_GREATERTHANOREQUAL: case MOJOSHADER_AST_OP_NOTEQUAL: case MOJOSHADER_AST_OP_EQUAL: datatype = type_check_ast(ctx, ast->binary.left); datatype2 = type_check_ast(ctx, ast->binary.right); add_type_coercion(ctx, &ast->binary.left, datatype, &ast->binary.right, datatype2); ast->binary.datatype = &ctx->dt_bool; return ast->binary.datatype; case MOJOSHADER_AST_OP_BINARYAND: case MOJOSHADER_AST_OP_BINARYXOR: case MOJOSHADER_AST_OP_BINARYOR: datatype = type_check_ast(ctx, ast->binary.left); datatype2 = type_check_ast(ctx, ast->binary.right); require_integer_datatype(ctx, datatype); require_integer_datatype(ctx, datatype2); ast->binary.datatype = add_type_coercion(ctx, &ast->binary.left, datatype, &ast->binary.right, datatype2); return ast->binary.datatype; case MOJOSHADER_AST_OP_LOGICALAND: case MOJOSHADER_AST_OP_LOGICALOR: datatype = type_check_ast(ctx, ast->binary.left); datatype2 = type_check_ast(ctx, ast->binary.right); require_boolean_datatype(ctx, datatype); require_boolean_datatype(ctx, datatype2); add_type_coercion(ctx, &ast->binary.left, datatype, &ast->binary.right, datatype2); ast->binary.datatype = &ctx->dt_bool; case MOJOSHADER_AST_OP_ASSIGN: case MOJOSHADER_AST_OP_MULASSIGN: case MOJOSHADER_AST_OP_DIVASSIGN: case MOJOSHADER_AST_OP_MODASSIGN: case MOJOSHADER_AST_OP_ADDASSIGN: case MOJOSHADER_AST_OP_SUBASSIGN: case MOJOSHADER_AST_OP_LSHIFTASSIGN: case MOJOSHADER_AST_OP_RSHIFTASSIGN: case MOJOSHADER_AST_OP_ANDASSIGN: case MOJOSHADER_AST_OP_XORASSIGN: case MOJOSHADER_AST_OP_ORASSIGN: datatype = type_check_ast(ctx, ast->binary.left); datatype2 = type_check_ast(ctx, ast->binary.right); ast->binary.datatype = add_type_coercion(ctx, NULL, datatype, &ast->binary.right, datatype2); return ast->binary.datatype; case MOJOSHADER_AST_OP_CONDITIONAL: datatype = type_check_ast(ctx, ast->ternary.left); datatype2 = type_check_ast(ctx, ast->ternary.center); datatype3 = type_check_ast(ctx, ast->ternary.right); require_numeric_datatype(ctx, datatype); ast->ternary.datatype = add_type_coercion(ctx, &ast->ternary.center, datatype2, &ast->ternary.right, datatype3); return ast->ternary.datatype; case MOJOSHADER_AST_OP_IDENTIFIER: datatype = find_variable(ctx, ast->identifier.identifier); if (datatype == NULL) { fail(ctx, "Unknown identifier"); // !!! FIXME: replace with a sane default, move on. datatype = &ctx->dt_int; } // if ast->identifier.datatype = datatype; return ast->identifier.datatype; case MOJOSHADER_AST_OP_INT_LITERAL: case MOJOSHADER_AST_OP_FLOAT_LITERAL: case MOJOSHADER_AST_OP_STRING_LITERAL: case MOJOSHADER_AST_OP_BOOLEAN_LITERAL: assert(ast->expression.datatype != NULL); return ast->expression.datatype; // already set up during parsing. case MOJOSHADER_AST_ARGUMENTS: assert(0 && "Should be done by MOJOSHADER_AST_OP_CALLFUNC/CONSTRUCTOR"); return NULL; case MOJOSHADER_AST_OP_CALLFUNC: { datatype = type_check_ast(ctx, ast->callfunc.identifier); const MOJOSHADER_astDataType *reduced = reduce_datatype(datatype); require_function_datatype(ctx, reduced); // !!! FIXME: replace with an int literal if this isn't a function. MOJOSHADER_astArguments *arg = ast->callfunc.args; MOJOSHADER_astArguments *prev = NULL; int i; for (i = 0; i < reduced->function.num_params; i++) { if (arg == NULL) // !!! FIXME: check for default parameters. { fail(ctx, "Too few arguments"); // !!! FIXME: replace AST here. break; } // if datatype2 = type_check_ast(ctx, arg->argument); add_type_coercion(ctx, NULL, &reduced->function.params[i], &arg->argument, datatype2); prev = arg; arg = arg->next; } // for if (arg != NULL) { // Process extra arguments then chop them out. MOJOSHADER_astArguments *argi; for (argi = arg; argi != NULL; argi = argi->next) type_check_ast(ctx, argi->argument); if (prev != NULL) prev->next = NULL; delete_arguments(ctx, arg); fail(ctx, "Too many arguments"); } // if ast->callfunc.datatype = reduced->function.retval; return ast->callfunc.datatype; } // case case MOJOSHADER_AST_OP_CONSTRUCTOR: { const MOJOSHADER_astDataType *reduced = reduce_datatype(ast->constructor.datatype); const MOJOSHADER_astDataType *base_dt = reduced; int num_params = 1; assert(reduced != NULL); switch (reduced->type) { case MOJOSHADER_AST_DATATYPE_VECTOR: num_params = reduced->vector.elements; base_dt = reduced->vector.base; break; case MOJOSHADER_AST_DATATYPE_MATRIX: num_params = reduced->matrix.rows * reduced->matrix.columns; base_dt = reduced->matrix.base; break; case MOJOSHADER_AST_DATATYPE_BOOL: case MOJOSHADER_AST_DATATYPE_INT: case MOJOSHADER_AST_DATATYPE_UINT: case MOJOSHADER_AST_DATATYPE_FLOAT: case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM: case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM: case MOJOSHADER_AST_DATATYPE_HALF: case MOJOSHADER_AST_DATATYPE_DOUBLE: case MOJOSHADER_AST_DATATYPE_STRING: num_params = 1; break; // !!! FIXME: can you construct a MOJOSHADER_AST_DATATYPE_STRUCT? // !!! FIXME: can you construct a MOJOSHADER_AST_DATATYPE_ARRAY? // !!! FIXME: can you construct a MOJOSHADER_AST_DATATYPE_BUFFER? default: fail(ctx, "Invalid type for constructor"); delete_arguments(ctx, ast->constructor.args); ast->constructor.args = new_argument(ctx, new_literal_int_expr(ctx, 0)); ast->constructor.datatype = &ctx->dt_int; return ast->constructor.datatype; } // switch assert(num_params > 0); MOJOSHADER_astArguments *arg = ast->constructor.args; MOJOSHADER_astArguments *prev = NULL; int i; for (i = 0; i < num_params; i++) { if (arg == NULL) // !!! FIXME: check for default parameters. { fail(ctx, "Too few arguments"); // !!! FIXME: replace AST here. break; } // if datatype2 = type_check_ast(ctx, arg->argument); add_type_coercion(ctx, NULL, base_dt, &arg->argument, datatype2); prev = arg; arg = arg->next; } // for if (arg != NULL) { fail(ctx, "Too many arguments"); // Process extra arguments then chop them out. MOJOSHADER_astArguments *argi; for (argi = arg; argi != NULL; argi = argi->next) type_check_ast(ctx, argi->argument); if (prev != NULL) prev->next = NULL; delete_arguments(ctx, arg); } // if return ast->constructor.datatype; } // case case MOJOSHADER_AST_OP_CAST: datatype = ast->cast.datatype; datatype2 = type_check_ast(ctx, ast->cast.operand); // you still need type coercion, since you could do a wrong cast, // like "int x = (short) mychar;" add_type_coercion(ctx, NULL, datatype, &ast->cast.operand, datatype2); return datatype; case MOJOSHADER_AST_STATEMENT_BREAK: case MOJOSHADER_AST_STATEMENT_CONTINUE: case MOJOSHADER_AST_STATEMENT_DISCARD: case MOJOSHADER_AST_STATEMENT_EMPTY: type_check_ast(ctx, ast->stmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_EXPRESSION: // !!! FIXME: warn about expressions without a side-effect here? type_check_ast(ctx, ast->exprstmt.expr); // !!! FIXME: This is named badly... type_check_ast(ctx, ast->exprstmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_IF: push_scope(ctx); // new scope for "if ((int x = blah()) != 0)" type_check_ast(ctx, ast->ifstmt.expr); type_check_ast(ctx, ast->ifstmt.statement); pop_scope(ctx); type_check_ast(ctx, ast->ifstmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_TYPEDEF: type_check_ast(ctx, ast->typedefstmt.type_info); type_check_ast(ctx, ast->typedefstmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_SWITCH: { MOJOSHADER_astSwitchCases *cases = ast->switchstmt.cases; datatype = type_check_ast(ctx, ast->switchstmt.expr); while (cases) { datatype2 = type_check_ast(ctx, cases->expr); add_type_coercion(ctx, NULL, datatype, &cases->expr, datatype2); type_check_ast(ctx, cases->statement); cases = cases->next; } // while return NULL; } // case case MOJOSHADER_AST_SWITCH_CASE: assert(0 && "Should be done by MOJOSHADER_AST_STATEMENT_SWITCH."); return NULL; case MOJOSHADER_AST_STATEMENT_STRUCT: type_check_ast(ctx, ast->structstmt.struct_info); type_check_ast(ctx, ast->structstmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_VARDECL: type_check_ast(ctx, ast->vardeclstmt.declaration); type_check_ast(ctx, ast->vardeclstmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_BLOCK: push_scope(ctx); // new vars declared here live until '}'. type_check_ast(ctx, ast->blockstmt.statements); pop_scope(ctx); type_check_ast(ctx, ast->blockstmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_FOR: push_scope(ctx); // new scope for "for (int x = 0; ...)" type_check_ast(ctx, ast->forstmt.var_decl); type_check_ast(ctx, ast->forstmt.initializer); type_check_ast(ctx, ast->forstmt.looptest); type_check_ast(ctx, ast->forstmt.counter); type_check_ast(ctx, ast->forstmt.statement); pop_scope(ctx); type_check_ast(ctx, ast->forstmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_DO: type_check_ast(ctx, ast->dostmt.statement); push_scope(ctx); // new scope for "while ((int x = blah()) != 0)" type_check_ast(ctx, ast->dostmt.expr); pop_scope(ctx); type_check_ast(ctx, ast->dostmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_WHILE: push_scope(ctx); // new scope for "while ((int x = blah()) != 0)" type_check_ast(ctx, ast->whilestmt.expr); type_check_ast(ctx, ast->whilestmt.statement); pop_scope(ctx); type_check_ast(ctx, ast->whilestmt.next); return NULL; case MOJOSHADER_AST_STATEMENT_RETURN: type_check_ast(ctx, ast->returnstmt.expr); type_check_ast(ctx, ast->returnstmt.next); case MOJOSHADER_AST_COMPUNIT_FUNCTION: // !!! FIXME: this is totally broken for function overloading. //fsdfsdf datatype = get_usertype(ctx, ast->funcunit.declaration->identifier); if (datatype == NULL) { // add function declaration if we've not seen it. datatype = ast->funcunit.declaration->datatype; push_usertype(ctx, ast->funcunit.declaration->identifier, datatype); } // if // declarations can be done multiple times if they match. else if (datatype != ast->funcunit.declaration->datatype) { // !!! FIXME: function overloading is legal. fail(ctx, "function sigs don't match"); } // else push_scope(ctx); // so function params are in function scope. type_check_ast(ctx, ast->funcunit.declaration); if (ast->funcunit.definition == NULL) pop_scope(ctx); else { type_check_ast(ctx, ast->funcunit.definition); pop_scope(ctx); push_variable(ctx, ast->funcunit.declaration->identifier, datatype); } // else type_check_ast(ctx, ast->funcunit.next); return NULL; case MOJOSHADER_AST_COMPUNIT_TYPEDEF: type_check_ast(ctx, ast->typedefunit.type_info); type_check_ast(ctx, ast->typedefunit.next); return NULL; case MOJOSHADER_AST_COMPUNIT_STRUCT: type_check_ast(ctx, ast->structunit.struct_info); type_check_ast(ctx, ast->structunit.next); return NULL; case MOJOSHADER_AST_COMPUNIT_VARIABLE: type_check_ast(ctx, ast->varunit.declaration); type_check_ast(ctx, ast->varunit.next); return NULL; case MOJOSHADER_AST_SCALAR_OR_ARRAY: assert(0 && "Should be done by other AST nodes."); return NULL; case MOJOSHADER_AST_TYPEDEF: { MOJOSHADER_astScalarOrArray *soa = ast->typdef.details; datatype = get_usertype(ctx, soa->identifier); if (datatype != NULL) { fail(ctx, "typedef already defined"); ast->typdef.datatype = datatype; return datatype; } // if datatype = build_datatype(ctx, ast->typdef.isconst, ast->typdef.datatype, soa); if (datatype == NULL) return NULL; // out of memory? push_usertype(ctx, soa->identifier, datatype); ast->typdef.datatype = datatype; return ast->typdef.datatype; } // case case MOJOSHADER_AST_FUNCTION_PARAMS: assert(0 && "Should be done by MOJOSHADER_AST_FUNCTION_SIGNATURE"); case MOJOSHADER_AST_FUNCTION_SIGNATURE: { MOJOSHADER_astFunctionParameters *param; int count = 0; // !!! FIXME: pre-count this? for (param = ast->funcsig.params; param; param = param->next) count++; // !!! FIXME: this is hacky. MOJOSHADER_astDataType *dtparams; void *ptr = Malloc(ctx, sizeof (*dtparams) * count); if (ptr == NULL) return NULL; if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr))) { Free(ctx, ptr); return NULL; } // if dtparams = (MOJOSHADER_astDataType *) ptr; ptr = Malloc(ctx, sizeof (MOJOSHADER_astDataType)); if (ptr == NULL) return NULL; if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr))) { Free(ctx, ptr); return NULL; } // if MOJOSHADER_astDataType *dt = (MOJOSHADER_astDataType *) ptr; int i = 0; for (param = ast->funcsig.params; param; param = param->next) { assert(i < count); push_variable(ctx, param->identifier, param->datatype); datatype2 = type_check_ast(ctx, param->initializer); add_type_coercion(ctx, NULL, param->datatype, ¶m->initializer, datatype2); memcpy(&dtparams[i], param->datatype, sizeof (*param->datatype)); i++; } // for dt->type = MOJOSHADER_AST_DATATYPE_FUNCTION; dt->function.retval = ast->funcsig.datatype; dt->function.params = dtparams; dt->function.num_params = count; ast->funcsig.datatype = dt; return ast->funcsig.datatype; } // case case MOJOSHADER_AST_STRUCT_DECLARATION: { const MOJOSHADER_astStructMembers *mbrs; // !!! FIXME: count this during parsing? int count = 0; mbrs = ast->structdecl.members; while (mbrs != NULL) { count++; mbrs = mbrs->next; } // while // !!! FIXME: this is hacky. MOJOSHADER_astDataTypeStructMember *dtmbrs; void *ptr = Malloc(ctx, sizeof (*dtmbrs) * count); if (ptr == NULL) return NULL; if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr))) { Free(ctx, ptr); return NULL; } // if dtmbrs = (MOJOSHADER_astDataTypeStructMember *) ptr; ptr = Malloc(ctx, sizeof (MOJOSHADER_astDataType)); if (ptr == NULL) return NULL; if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr))) { Free(ctx, ptr); return NULL; } // if MOJOSHADER_astDataType *dt = (MOJOSHADER_astDataType *) ptr; mbrs = ast->structdecl.members; int i; for (i = 0; i < count; i++) { // !!! FIXME: current grammar forbids const keyword on struct members! dtmbrs[i].datatype = build_datatype(ctx, 0, mbrs->datatype, mbrs->details); dtmbrs[i].identifier = mbrs->details->identifier; // cached! mbrs = mbrs->next; } // for dt->structure.type = MOJOSHADER_AST_DATATYPE_STRUCT; dt->structure.members = dtmbrs; dt->structure.member_count = count; ast->structdecl.datatype = dt; push_usertype(ctx, ast->structdecl.name, ast->structdecl.datatype); return ast->structdecl.datatype; } // case case MOJOSHADER_AST_STRUCT_MEMBER: assert(0 && "Should be done by MOJOSHADER_AST_STRUCT_DECLARATION."); return NULL; case MOJOSHADER_AST_VARIABLE_DECLARATION: // this is true now, but we'll fill in ->datatype no matter what. assert((ast->vardecl.datatype && !ast->vardecl.anonymous_datatype) || (!ast->vardecl.datatype && ast->vardecl.anonymous_datatype)); // An anonymous struct? That AST node does the heavy lifting. if (ast->vardecl.anonymous_datatype != NULL) datatype = type_check_ast(ctx, ast->vardecl.anonymous_datatype); else { datatype = build_datatype(ctx, (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_CONST) != 0, ast->vardecl.datatype, ast->vardecl.details); } // else ast->vardecl.datatype = datatype; push_variable(ctx, ast->vardecl.details->identifier, datatype); if (ast->vardecl.initializer != NULL) { datatype2 = type_check_ast(ctx, ast->vardecl.initializer); add_type_coercion(ctx, NULL, datatype, &ast->vardecl.initializer, datatype2); } // if type_check_ast(ctx, ast->vardecl.annotations); type_check_ast(ctx, ast->vardecl.lowlevel); datatype2 = type_check_ast(ctx, ast->vardecl.next); return ast->vardecl.datatype; case MOJOSHADER_AST_ANNOTATION: { MOJOSHADER_astAnnotations *anno = &ast->annotations; while (anno) { type_check_ast(ctx, anno->initializer); anno = anno->next; } // while return NULL; } // case case MOJOSHADER_AST_PACK_OFFSET: case MOJOSHADER_AST_VARIABLE_LOWLEVEL: return NULL; // no-op (for now, at least). default: assert(0 && "unexpected type"); } // switch return NULL; } // type_check_ast static inline void semantic_analysis(Context *ctx) { type_check_ast(ctx, ctx->ast); // !!! FIXME: build an IR here. // done with the AST, nuke it. delete_compilation_unit(ctx, (MOJOSHADER_astCompilationUnit *) ctx->ast); ctx->ast = NULL; // !!! FIXME: do everything else. :) } // semantic_analysis // !!! FIXME: isn't this a cut-and-paste of somewhere else? static inline int64 strtoi64(const char *str, unsigned int len) { int64 retval = 0; int64 mult = 1; int i = 0; while ((len) && (*str == ' ')) { str++; len--; } // while if ((len) && (*str == '-')) { mult = -1; str++; len--; } // if while (i < len) { const char ch = str[i]; if ((ch < '0') || (ch > '9')) break; i++; } // while while (--i >= 0) { const char ch = str[i]; retval += ((int64) (ch - '0')) * mult; mult *= 10; } // while return retval; } // strtoi64 // !!! FIXME: isn't this a cut-and-paste of somewhere else? static inline double strtodouble(const char *_str, unsigned int len) { // !!! FIXME: laziness prevails. char *str = (char *) alloca(len+1); memcpy(str, _str, len); str[len] = '\0'; return strtod(str, NULL); } // strtodouble #if 0 // This does not check correctness (POSITIONT993842 passes, etc). static int is_semantic(const Context *ctx, const char *token, const unsigned int tokenlen) { static const char *names[] = { "BINORMAL", "BLENDINDICES", "BLENDWEIGHT", "COLOR", "NORMAL", "POSITION", "POSITIONT", "PSIZE", "TANGENT", "TEXCOORD", "FOG", "TESSFACTOR", "TEXCOORD", "VFACE", "VPOS", "DEPTH", NULL }; // !!! FIXME: DX10 has SV_* ("System Value Semantics"). const char **i; for (i = names; *i; i++) { const char *name = *i; const size_t namelen = strlen(name); if (tokenlen < namelen) continue; else if (memcmp(token, name, namelen) != 0) continue; for (name += namelen; *name; name++) { if ((*name < '0') || (*name > '9')) break; } // for if (*name == '\0') return 1; } // for return 0; } // is_semantic #endif static int convert_to_lemon_token(Context *ctx, const char *token, unsigned int tokenlen, const Token tokenval) { switch (tokenval) { case ((Token) ','): return TOKEN_HLSL_COMMA; case ((Token) '='): return TOKEN_HLSL_ASSIGN; case ((Token) TOKEN_ADDASSIGN): return TOKEN_HLSL_ADDASSIGN; case ((Token) TOKEN_SUBASSIGN): return TOKEN_HLSL_SUBASSIGN; case ((Token) TOKEN_MULTASSIGN): return TOKEN_HLSL_MULASSIGN; case ((Token) TOKEN_DIVASSIGN): return TOKEN_HLSL_DIVASSIGN; case ((Token) TOKEN_MODASSIGN): return TOKEN_HLSL_MODASSIGN; case ((Token) TOKEN_LSHIFTASSIGN): return TOKEN_HLSL_LSHIFTASSIGN; case ((Token) TOKEN_RSHIFTASSIGN): return TOKEN_HLSL_RSHIFTASSIGN; case ((Token) TOKEN_ANDASSIGN): return TOKEN_HLSL_ANDASSIGN; case ((Token) TOKEN_ORASSIGN): return TOKEN_HLSL_ORASSIGN; case ((Token) TOKEN_XORASSIGN): return TOKEN_HLSL_XORASSIGN; case ((Token) '?'): return TOKEN_HLSL_QUESTION; case ((Token) TOKEN_OROR): return TOKEN_HLSL_OROR; case ((Token) TOKEN_ANDAND): return TOKEN_HLSL_ANDAND; case ((Token) '|'): return TOKEN_HLSL_OR; case ((Token) '^'): return TOKEN_HLSL_XOR; case ((Token) '&'): return TOKEN_HLSL_AND; case ((Token) TOKEN_EQL): return TOKEN_HLSL_EQL; case ((Token) TOKEN_NEQ): return TOKEN_HLSL_NEQ; case ((Token) '<'): return TOKEN_HLSL_LT; case ((Token) TOKEN_LEQ): return TOKEN_HLSL_LEQ; case ((Token) '>'): return TOKEN_HLSL_GT; case ((Token) TOKEN_GEQ): return TOKEN_HLSL_GEQ; case ((Token) TOKEN_LSHIFT): return TOKEN_HLSL_LSHIFT; case ((Token) TOKEN_RSHIFT): return TOKEN_HLSL_RSHIFT; case ((Token) '+'): return TOKEN_HLSL_PLUS; case ((Token) '-'): return TOKEN_HLSL_MINUS; case ((Token) '*'): return TOKEN_HLSL_STAR; case ((Token) '/'): return TOKEN_HLSL_SLASH; case ((Token) '%'): return TOKEN_HLSL_PERCENT; case ((Token) '!'): return TOKEN_HLSL_EXCLAMATION; case ((Token) '~'): return TOKEN_HLSL_COMPLEMENT; case ((Token) TOKEN_DECREMENT): return TOKEN_HLSL_MINUSMINUS; case ((Token) TOKEN_INCREMENT): return TOKEN_HLSL_PLUSPLUS; case ((Token) '.'): return TOKEN_HLSL_DOT; case ((Token) '['): return TOKEN_HLSL_LBRACKET; case ((Token) ']'): return TOKEN_HLSL_RBRACKET; case ((Token) '('): return TOKEN_HLSL_LPAREN; case ((Token) ')'): return TOKEN_HLSL_RPAREN; case ((Token) TOKEN_INT_LITERAL): return TOKEN_HLSL_INT_CONSTANT; case ((Token) TOKEN_FLOAT_LITERAL): return TOKEN_HLSL_FLOAT_CONSTANT; case ((Token) TOKEN_STRING_LITERAL): return TOKEN_HLSL_STRING_LITERAL; case ((Token) ':'): return TOKEN_HLSL_COLON; case ((Token) ';'): return TOKEN_HLSL_SEMICOLON; case ((Token) '{'): return TOKEN_HLSL_LBRACE; case ((Token) '}'): return TOKEN_HLSL_RBRACE; //case ((Token) TOKEN_PP_PRAGMA): return TOKEN_HLSL_PRAGMA; //case ((Token) '\n'): return TOKEN_HLSL_NEWLINE; case ((Token) TOKEN_IDENTIFIER): #define tokencmp(t) ((tokenlen == strlen(t)) && (memcmp(token, t, tokenlen) == 0)) //case ((Token) ''): return TOKEN_HLSL_TYPECAST //if (tokencmp("")) return TOKEN_HLSL_TYPE_NAME //if (tokencmp("...")) return TOKEN_HLSL_ELIPSIS if (tokencmp("else")) return TOKEN_HLSL_ELSE; if (tokencmp("inline")) return TOKEN_HLSL_INLINE; if (tokencmp("void")) return TOKEN_HLSL_VOID; if (tokencmp("in")) return TOKEN_HLSL_IN; if (tokencmp("inout")) return TOKEN_HLSL_INOUT; if (tokencmp("out")) return TOKEN_HLSL_OUT; if (tokencmp("uniform")) return TOKEN_HLSL_UNIFORM; if (tokencmp("linear")) return TOKEN_HLSL_LINEAR; if (tokencmp("centroid")) return TOKEN_HLSL_CENTROID; if (tokencmp("nointerpolation")) return TOKEN_HLSL_NOINTERPOLATION; if (tokencmp("noperspective")) return TOKEN_HLSL_NOPERSPECTIVE; if (tokencmp("sample")) return TOKEN_HLSL_SAMPLE; if (tokencmp("struct")) return TOKEN_HLSL_STRUCT; if (tokencmp("typedef")) return TOKEN_HLSL_TYPEDEF; if (tokencmp("const")) return TOKEN_HLSL_CONST; if (tokencmp("packoffset")) return TOKEN_HLSL_PACKOFFSET; if (tokencmp("register")) return TOKEN_HLSL_REGISTER; if (tokencmp("extern")) return TOKEN_HLSL_EXTERN; if (tokencmp("shared")) return TOKEN_HLSL_SHARED; if (tokencmp("static")) return TOKEN_HLSL_STATIC; if (tokencmp("volatile")) return TOKEN_HLSL_VOLATILE; if (tokencmp("row_major")) return TOKEN_HLSL_ROWMAJOR; if (tokencmp("column_major")) return TOKEN_HLSL_COLUMNMAJOR; if (tokencmp("bool")) return TOKEN_HLSL_BOOL; if (tokencmp("int")) return TOKEN_HLSL_INT; if (tokencmp("uint")) return TOKEN_HLSL_UINT; if (tokencmp("half")) return TOKEN_HLSL_HALF; if (tokencmp("float")) return TOKEN_HLSL_FLOAT; if (tokencmp("double")) return TOKEN_HLSL_DOUBLE; if (tokencmp("string")) return TOKEN_HLSL_STRING; if (tokencmp("snorm")) return TOKEN_HLSL_SNORM; if (tokencmp("unorm")) return TOKEN_HLSL_UNORM; if (tokencmp("buffer")) return TOKEN_HLSL_BUFFER; if (tokencmp("vector")) return TOKEN_HLSL_VECTOR; if (tokencmp("matrix")) return TOKEN_HLSL_MATRIX; if (tokencmp("break")) return TOKEN_HLSL_BREAK; if (tokencmp("continue")) return TOKEN_HLSL_CONTINUE; if (tokencmp("discard")) return TOKEN_HLSL_DISCARD; if (tokencmp("return")) return TOKEN_HLSL_RETURN; if (tokencmp("while")) return TOKEN_HLSL_WHILE; if (tokencmp("for")) return TOKEN_HLSL_FOR; if (tokencmp("unroll")) return TOKEN_HLSL_UNROLL; if (tokencmp("loop")) return TOKEN_HLSL_LOOP; if (tokencmp("do")) return TOKEN_HLSL_DO; if (tokencmp("if")) return TOKEN_HLSL_IF; if (tokencmp("branch")) return TOKEN_HLSL_BRANCH; if (tokencmp("flatten")) return TOKEN_HLSL_FLATTEN; if (tokencmp("switch")) return TOKEN_HLSL_SWITCH; if (tokencmp("forcecase")) return TOKEN_HLSL_FORCECASE; if (tokencmp("call")) return TOKEN_HLSL_CALL; if (tokencmp("case")) return TOKEN_HLSL_CASE; if (tokencmp("default")) return TOKEN_HLSL_DEFAULT; if (tokencmp("sampler")) return TOKEN_HLSL_SAMPLER; if (tokencmp("sampler1D")) return TOKEN_HLSL_SAMPLER1D; if (tokencmp("sampler2D")) return TOKEN_HLSL_SAMPLER2D; if (tokencmp("sampler3D")) return TOKEN_HLSL_SAMPLER3D; if (tokencmp("samplerCUBE")) return TOKEN_HLSL_SAMPLERCUBE; if (tokencmp("sampler_state")) return TOKEN_HLSL_SAMPLER_STATE; if (tokencmp("SamplerState")) return TOKEN_HLSL_SAMPLERSTATE; if (tokencmp("true")) return TOKEN_HLSL_TRUE; if (tokencmp("false")) return TOKEN_HLSL_FALSE; if (tokencmp("SamplerComparisonState")) return TOKEN_HLSL_SAMPLERCOMPARISONSTATE; if (tokencmp("isolate")) return TOKEN_HLSL_ISOLATE; if (tokencmp("maxInstructionCount")) return TOKEN_HLSL_MAXINSTRUCTIONCOUNT; if (tokencmp("noExpressionOptimizations")) return TOKEN_HLSL_NOEXPRESSIONOPTIMIZATIONS; if (tokencmp("unused")) return TOKEN_HLSL_UNUSED; if (tokencmp("xps")) return TOKEN_HLSL_XPS; #undef tokencmp // get a canonical copy of the string now, as we'll need it. token = stringcache_len(ctx->strcache, token, tokenlen); if (get_usertype(ctx, token) != NULL) return TOKEN_HLSL_USERTYPE; return TOKEN_HLSL_IDENTIFIER; case TOKEN_EOI: return 0; default: assert(0 && "unexpected token from lexer\n"); return 0; } // switch return 0; } // convert_to_lemon_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; // !!! FIXME: this is kinda hacky. const size_t count = buffer_size(ctx->garbage) / sizeof (void *); if (count > 0) { void **garbage = (void **) buffer_flatten(ctx->garbage); if (garbage != NULL) { size_t i; for (i = 0; i < count; i++) f(garbage[i], d); f(garbage, d); } // if } // if buffer_destroy(ctx->garbage); delete_compilation_unit(ctx, (MOJOSHADER_astCompilationUnit*)ctx->ast); destroy_symbolmap(ctx, &ctx->usertypes); destroy_symbolmap(ctx, &ctx->variables); stringcache_destroy(ctx->strcache); errorlist_destroy(ctx->errors); errorlist_destroy(ctx->warnings); // !!! FIXME: more to clean up here, now. f(ctx, d); } // if } // destroy_context static Context *build_context(MOJOSHADER_malloc m, MOJOSHADER_free f, void *d) { if (!m) m = MOJOSHADER_internal_malloc; if (!f) f = MOJOSHADER_internal_free; 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->parse_phase = MOJOSHADER_PARSEPHASE_NOTSTARTED; create_symbolmap(ctx, &ctx->usertypes); // !!! FIXME: check for failure. create_symbolmap(ctx, &ctx->variables); // !!! FIXME: check for failure. ctx->strcache = stringcache_create(MallocBridge, FreeBridge, ctx); // !!! FIXME: check for failure. ctx->errors = errorlist_create(MallocBridge, FreeBridge, ctx); // !!! FIXME: check for failure. ctx->warnings = errorlist_create(MallocBridge, FreeBridge, ctx); // !!! FIXME: check for failure. // !!! FIXME: this feels hacky. ctx->garbage = buffer_create(256*sizeof(void*),MallocBridge,FreeBridge,ctx); // !!! FIXME: check for failure. ctx->dt_bool.type = MOJOSHADER_AST_DATATYPE_BOOL; ctx->dt_int.type = MOJOSHADER_AST_DATATYPE_INT; ctx->dt_uint.type = MOJOSHADER_AST_DATATYPE_UINT; ctx->dt_float.type = MOJOSHADER_AST_DATATYPE_FLOAT; ctx->dt_float_snorm.type = MOJOSHADER_AST_DATATYPE_FLOAT_SNORM; ctx->dt_float_unorm.type = MOJOSHADER_AST_DATATYPE_FLOAT_UNORM; ctx->dt_half.type = MOJOSHADER_AST_DATATYPE_HALF; ctx->dt_double.type = MOJOSHADER_AST_DATATYPE_DOUBLE; ctx->dt_string.type = MOJOSHADER_AST_DATATYPE_STRING; ctx->dt_sampler1d.type = MOJOSHADER_AST_DATATYPE_SAMPLER_1D; ctx->dt_sampler2d.type = MOJOSHADER_AST_DATATYPE_SAMPLER_2D; ctx->dt_sampler3d.type = MOJOSHADER_AST_DATATYPE_SAMPLER_3D; ctx->dt_samplercube.type = MOJOSHADER_AST_DATATYPE_SAMPLER_CUBE; ctx->dt_samplerstate.type = MOJOSHADER_AST_DATATYPE_SAMPLER_STATE; ctx->dt_samplercompstate.type = MOJOSHADER_AST_DATATYPE_SAMPLER_COMPARISON_STATE; #define INIT_DT_BUFFER(t) \ ctx->dt_buf_##t.type = MOJOSHADER_AST_DATATYPE_BUFFER; \ ctx->dt_buf_##t.buffer.base = &ctx->dt_##t; INIT_DT_BUFFER(bool); INIT_DT_BUFFER(int); INIT_DT_BUFFER(uint); INIT_DT_BUFFER(half); INIT_DT_BUFFER(float); INIT_DT_BUFFER(double); INIT_DT_BUFFER(float_snorm); INIT_DT_BUFFER(float_unorm); #undef INIT_DT_BUFFER return ctx; } // build_context // parse the source code into an AST. static void parse_source(Context *ctx, 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) { TokenData data; unsigned int tokenlen; Token tokenval; const char *token; int lemon_token; const char *fname; Preprocessor *pp; void *parser; if (!include_open) include_open = MOJOSHADER_internal_include_open; if (!include_close) include_close = MOJOSHADER_internal_include_close; pp = preprocessor_start(filename, source, sourcelen, include_open, include_close, defines, define_count, 0, MallocBridge, FreeBridge, ctx); if (pp == NULL) { assert(ctx->out_of_memory); // shouldn't fail for any other reason. return; } // if parser = ParseHLSLAlloc(ctx->malloc, ctx->malloc_data); if (parser == NULL) { assert(ctx->out_of_memory); // shouldn't fail for any other reason. preprocessor_end(pp); return; } // if // !!! FIXME: check if (parser == NULL)... #if DEBUG_COMPILER_PARSER ParseHLSLTrace(stdout, "COMPILER: "); #endif // !!! FIXME: move this to a subroutine. // add in standard typedefs... const struct { const char *str; const MOJOSHADER_astDataType *datatype; } types[] = { { "bool", &ctx->dt_bool }, { "int", &ctx->dt_int }, { "uint", &ctx->dt_uint }, { "half", &ctx->dt_half }, { "float", &ctx->dt_float }, { "double", &ctx->dt_double }, }; int i, j, k; for (i = 0; i < STATICARRAYLEN(types); i++) { char buf[32]; int len; const MOJOSHADER_astDataType *dt; for (j = 1; j <= 4; j++) { // "float2" dt = new_datatype_vector(ctx, types[i].datatype, j); len = snprintf(buf, sizeof (buf), "%s%d", types[i].str, j); push_usertype(ctx, stringcache_len(ctx->strcache, buf, len), dt); for (k = 1; k <= 4; k++) { // "float2x2" dt = new_datatype_matrix(ctx, types[i].datatype, j, k); len = snprintf(buf, sizeof (buf), "%s%dx%d", types[i].str,j,k); push_usertype(ctx, stringcache_len(ctx->strcache,buf,len), dt); } // for } // for } // for // Run the preprocessor/lexer/parser... int is_pragma = 0; // !!! FIXME: remove this later when we can parse #pragma. int skipping = 0; // !!! FIXME: remove this later when we can parse #pragma. do { token = preprocessor_nexttoken(pp, &tokenlen, &tokenval); if (ctx->out_of_memory) break; fname = preprocessor_sourcepos(pp, &ctx->sourceline); ctx->sourcefile = fname ? stringcache(ctx->strcache, fname) : 0; if ((tokenval == TOKEN_HASH) || (tokenval == TOKEN_HASHHASH)) tokenval = TOKEN_BAD_CHARS; if (tokenval == TOKEN_BAD_CHARS) { fail(ctx, "Bad characters in source file"); continue; } // else if else if (tokenval == TOKEN_PREPROCESSING_ERROR) { fail(ctx, token); // this happens to be null-terminated. continue; } // else if else if (tokenval == TOKEN_PP_PRAGMA) { assert(!is_pragma); is_pragma = 1; skipping = 1; continue; } else if (tokenval == ((Token) '\n')) { assert(is_pragma); is_pragma = 0; skipping = 0; continue; } else if (skipping) { continue; } // !!! FIXME: this is a mess, decide who should be doing this stuff, and only do it once. lemon_token = convert_to_lemon_token(ctx, token, tokenlen, tokenval); switch (lemon_token) { case TOKEN_HLSL_INT_CONSTANT: data.i64 = strtoi64(token, tokenlen); break; case TOKEN_HLSL_FLOAT_CONSTANT: data.dbl = strtodouble(token, tokenlen); break; case TOKEN_HLSL_USERTYPE: data.string = stringcache_len(ctx->strcache, token, tokenlen); data.datatype = get_usertype(ctx, data.string); assert(data.datatype != NULL); break; case TOKEN_HLSL_STRING_LITERAL: case TOKEN_HLSL_IDENTIFIER: data.string = stringcache_len(ctx->strcache, token, tokenlen); break; default: data.i64 = 0; break; } // switch ParseHLSL(parser, lemon_token, data, ctx); // this probably isn't perfect, but it's good enough for surviving // the parse. We'll sort out correctness once we have a tree. if (lemon_token == TOKEN_HLSL_LBRACE) push_scope(ctx); else if (lemon_token == TOKEN_HLSL_RBRACE) pop_scope(ctx); } while (tokenval != TOKEN_EOI); ParseHLSLFree(parser, ctx->free, ctx->malloc_data); preprocessor_end(pp); } // parse_source static MOJOSHADER_astData MOJOSHADER_out_of_mem_ast_data = { 1, &MOJOSHADER_out_of_mem_error, 0, 0, 0, 0, 0, 0 }; // !!! FIXME: cut and paste from assembler. static const MOJOSHADER_astData *build_failed_ast(Context *ctx) { assert(isfail(ctx)); if (ctx->out_of_memory) return &MOJOSHADER_out_of_mem_ast_data; MOJOSHADER_astData *retval = NULL; retval = (MOJOSHADER_astData *) Malloc(ctx, sizeof (MOJOSHADER_astData)); if (retval == NULL) return &MOJOSHADER_out_of_mem_ast_data; memset(retval, '\0', sizeof (MOJOSHADER_astData)); retval->source_profile = ctx->source_profile; 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); return &MOJOSHADER_out_of_mem_ast_data; } // if return retval; } // build_failed_ast static const MOJOSHADER_astData *build_astdata(Context *ctx) { MOJOSHADER_astData *retval = NULL; if (ctx->out_of_memory) return &MOJOSHADER_out_of_mem_ast_data; retval = (MOJOSHADER_astData *) Malloc(ctx, sizeof (MOJOSHADER_astData)); if (retval == NULL) return &MOJOSHADER_out_of_mem_ast_data; memset(retval, '\0', sizeof (MOJOSHADER_astData)); 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; if (!isfail(ctx)) { retval->source_profile = ctx->source_profile; retval->ast = ctx->ast; } // if retval->error_count = errorlist_count(ctx->errors); retval->errors = errorlist_flatten(ctx->errors); if (ctx->out_of_memory) { Free(ctx, retval); return &MOJOSHADER_out_of_mem_ast_data; } // if retval->opaque = ctx; return retval; } // build_astdata static void choose_src_profile(Context *ctx, const char *srcprofile) { #define TEST_PROFILE(x) do { \ if (strcmp(srcprofile, x) == 0) { \ ctx->source_profile = x; \ return; \ } \ } while (0) TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_VS_1_1); TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_VS_2_0); TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_VS_3_0); TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_1); TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_2); TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_3); TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_4); TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_2_0); TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_3_0); #undef TEST_PROFILE fail(ctx, "Unknown profile"); } // choose_src_profile static MOJOSHADER_compileData MOJOSHADER_out_of_mem_compile_data = { 1, &MOJOSHADER_out_of_mem_error, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // !!! FIXME: cut and paste from assembler. static const MOJOSHADER_compileData *build_failed_compile(Context *ctx) { assert(isfail(ctx)); MOJOSHADER_compileData *retval = NULL; retval = (MOJOSHADER_compileData *) Malloc(ctx, sizeof (MOJOSHADER_compileData)); if (retval == NULL) return &MOJOSHADER_out_of_mem_compile_data; memset(retval, '\0', sizeof (MOJOSHADER_compileData)); 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->source_profile = ctx->source_profile; retval->error_count = errorlist_count(ctx->errors); retval->errors = errorlist_flatten(ctx->errors); retval->warning_count = errorlist_count(ctx->warnings); retval->warnings = errorlist_flatten(ctx->warnings); if (ctx->out_of_memory) // in case something failed up there. { MOJOSHADER_freeCompileData(retval); return &MOJOSHADER_out_of_mem_compile_data; } // if return retval; } // build_failed_compile static const MOJOSHADER_compileData *build_compiledata(Context *ctx) { assert(!isfail(ctx)); MOJOSHADER_compileData *retval = NULL; retval = (MOJOSHADER_compileData *) Malloc(ctx, sizeof (MOJOSHADER_compileData)); if (retval == NULL) return &MOJOSHADER_out_of_mem_compile_data; memset(retval, '\0', sizeof (MOJOSHADER_compileData)); 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->source_profile = ctx->source_profile; if (!isfail(ctx)) { // !!! FIXME: build output and output_len here. } // if if (!isfail(ctx)) { // !!! FIXME: build symbols and symbol_count here. } // if retval->error_count = errorlist_count(ctx->errors); retval->errors = errorlist_flatten(ctx->errors); retval->warning_count = errorlist_count(ctx->warnings); retval->warnings = errorlist_flatten(ctx->warnings); if (ctx->out_of_memory) // in case something failed up there. { MOJOSHADER_freeCompileData(retval); return &MOJOSHADER_out_of_mem_compile_data; } // if return retval; } // build_compiledata // API entry point... // !!! FIXME: move this (and a lot of other things) to mojoshader_ast.c. const MOJOSHADER_astData *MOJOSHADER_parseAst(const char *srcprofile, const char *filename, const char *source, unsigned int sourcelen, const MOJOSHADER_preprocessorDefine *defs, unsigned int define_count, MOJOSHADER_includeOpen include_open, MOJOSHADER_includeClose include_close, MOJOSHADER_malloc m, MOJOSHADER_free f, void *d) { const MOJOSHADER_astData *retval = NULL; Context *ctx = NULL; if ( ((m == NULL) && (f != NULL)) || ((m != NULL) && (f == NULL)) ) return &MOJOSHADER_out_of_mem_ast_data; // supply both or neither. ctx = build_context(m, f, d); if (ctx == NULL) return &MOJOSHADER_out_of_mem_ast_data; choose_src_profile(ctx, srcprofile); if (!isfail(ctx)) { parse_source(ctx, filename, source, sourcelen, defs, define_count, include_open, include_close); } // if if (!isfail(ctx)) retval = build_astdata(ctx); // ctx isn't destroyed yet! else { retval = (MOJOSHADER_astData *) build_failed_ast(ctx); destroy_context(ctx); } // else return retval; } // MOJOSHADER_parseAst void MOJOSHADER_freeAstData(const MOJOSHADER_astData *_data) { MOJOSHADER_astData *data = (MOJOSHADER_astData *) _data; if ((data == NULL) || (data == &MOJOSHADER_out_of_mem_ast_data)) return; // no-op. // !!! FIXME: this needs to live for deleting the stringcache and the ast. Context *ctx = (Context *) data->opaque; MOJOSHADER_free f = (data->free == NULL) ? MOJOSHADER_internal_free : data->free; void *d = data->malloc_data; int i; // we don't f(data->source_profile), because that's internal static data. for (i = 0; i < data->error_count; i++) { f((void *) data->errors[i].error, d); f((void *) data->errors[i].filename, d); } // for f((void *) data->errors, d); // don't delete data->ast (it'll delete with the context). f(data, d); destroy_context(ctx); // finally safe to destroy this. } // MOJOSHADER_freeAstData const MOJOSHADER_compileData *MOJOSHADER_compile(const char *srcprofile, const char *filename, const char *source, unsigned int sourcelen, const MOJOSHADER_preprocessorDefine *defs, unsigned int define_count, MOJOSHADER_includeOpen include_open, MOJOSHADER_includeClose include_close, MOJOSHADER_malloc m, MOJOSHADER_free f, void *d) { // !!! FIXME: cut and paste from MOJOSHADER_parseAst(). MOJOSHADER_compileData *retval = NULL; Context *ctx = NULL; if ( ((m == NULL) && (f != NULL)) || ((m != NULL) && (f == NULL)) ) return &MOJOSHADER_out_of_mem_compile_data; // supply both or neither. ctx = build_context(m, f, d); if (ctx == NULL) return &MOJOSHADER_out_of_mem_compile_data; choose_src_profile(ctx, srcprofile); if (!isfail(ctx)) { parse_source(ctx, filename, source, sourcelen, defs, define_count, include_open, include_close); } // if if (!isfail(ctx)) semantic_analysis(ctx); if (isfail(ctx)) retval = (MOJOSHADER_compileData *) build_failed_compile(ctx); else retval = (MOJOSHADER_compileData *) build_compiledata(ctx); destroy_context(ctx); return retval; } // MOJOSHADER_compile void MOJOSHADER_freeCompileData(const MOJOSHADER_compileData *_data) { MOJOSHADER_compileData *data = (MOJOSHADER_compileData *) _data; if ((data == NULL) || (data == &MOJOSHADER_out_of_mem_compile_data)) return; // no-op. MOJOSHADER_free f = (data->free == NULL) ? MOJOSHADER_internal_free : data->free; void *d = data->malloc_data; int i; // we don't f(data->source_profile), because that's internal static data. for (i = 0; i < data->error_count; i++) { f((void *) data->errors[i].error, d); f((void *) data->errors[i].filename, d); } // for f((void *) data->errors, d); for (i = 0; i < data->warning_count; i++) { f((void *) data->warnings[i].error, d); f((void *) data->warnings[i].filename, d); } // for f((void *) data->warnings, d); for (i = 0; i < data->symbol_count; i++) { f((void *) data->symbols[i].name, d); f((void *) data->symbols[i].default_value, d); } // for f((void *) data->symbols, d); f((void *) data->output, d); f(data, d); } // MOJOSHADER_freeCompileData // end of mojoshader_compiler.c ...