diff options
Diffstat (limited to 'Externals/mojoshader/1067/mojoshader_compiler.c')
| -rw-r--r-- | Externals/mojoshader/1067/mojoshader_compiler.c | 6288 |
1 files changed, 6288 insertions, 0 deletions
diff --git a/Externals/mojoshader/1067/mojoshader_compiler.c b/Externals/mojoshader/1067/mojoshader_compiler.c new file mode 100644 index 00000000..6f6e11c2 --- /dev/null +++ b/Externals/mojoshader/1067/mojoshader_compiler.c @@ -0,0 +1,6288 @@ +/** + * 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; + int index; // unique positive value within a function, negative if global. + int referenced; // non-zero if something looked for this symbol (so we know it's used). + struct SymbolScope *next; +} SymbolScope; + +typedef struct SymbolMap +{ + HashTable *hash; + SymbolScope *scope; +} SymbolMap; + +typedef struct LoopLabels +{ + int start; // loop's start label during IR build. + int end; // loop's end label during IR build. + struct LoopLabels *prev; +} LoopLabels; + +// 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; + int is_func_scope; // non-zero if semantic analysis is in function scope. + int loop_count; + int switch_count; + int var_index; // next variable index for current function. + int global_var_index; // next variable index for global scope. + int user_func_index; // next function index for user-defined functions. + int intrinsic_func_index; // next function index for intrinsic functions. + + MOJOSHADER_irStatement **ir; // intermediate representation. + int ir_label_count; // next unused IR label index. + int ir_temp_count; // next unused IR temporary value index. + int ir_end; // current function's end label during IR build. + int ir_ret; // temp that holds current function's retval during IR build. + LoopLabels *ir_loop; // nested loop boundary labels during IR build. + + // Cache intrinsic types for fast lookup and consistent pointer values. + MOJOSHADER_astDataType dt_none; + 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 int datatypes_match(const MOJOSHADER_astDataType *a, + const MOJOSHADER_astDataType *b) +{ + int i; + + if (a == b) + return 1; + else if (a->type != b->type) + return 0; + + switch (a->type) + { + case MOJOSHADER_AST_DATATYPE_STRUCT: + if (a->structure.member_count != b->structure.member_count) + return 0; + for (i = 0; i < a->structure.member_count; i++) + { + if (!datatypes_match(a->structure.members[i].datatype, + b->structure.members[i].datatype)) + return 0; + // stringcache'd, pointer compare is safe. + else if (a->structure.members[i].identifier != + b->structure.members[i].identifier) + return 0; + } // for + return 1; + + case MOJOSHADER_AST_DATATYPE_ARRAY: + if (a->array.elements != b->array.elements) + return 0; + else if (!datatypes_match(a->array.base, b->array.base)) + return 0; + return 1; + + case MOJOSHADER_AST_DATATYPE_VECTOR: + if (a->vector.elements != b->vector.elements) + return 0; + else if (!datatypes_match(a->vector.base, b->vector.base)) + return 0; + return 1; + + case MOJOSHADER_AST_DATATYPE_MATRIX: + if (a->matrix.rows != b->matrix.rows) + return 0; + else if (a->matrix.columns != b->matrix.columns) + return 0; + else if (!datatypes_match(a->matrix.base, b->matrix.base)) + return 0; + return 1; + + case MOJOSHADER_AST_DATATYPE_BUFFER: + return datatypes_match(a->buffer.base, b->buffer.base); + + case MOJOSHADER_AST_DATATYPE_FUNCTION: + if (a->function.num_params != b->function.num_params) + return 0; + else if (a->function.intrinsic != b->function.intrinsic) + return 0; + else if (!datatypes_match(a->function.retval, b->function.retval)) + return 0; + for (i = 0; i < a->function.num_params; i++) + { + if (!datatypes_match(a->function.params[i], b->function.params[i])) + return 0; + } // for + return 1; + + case MOJOSHADER_AST_DATATYPE_USER: + return 0; // pointers must match, this clearly didn't. + + default: + assert(0 && "unexpected case"); + return 0; + } // switch + + return 0; +} // datatypes_match + +static void push_symbol(Context *ctx, SymbolMap *map, const char *sym, + const MOJOSHADER_astDataType *dt, const int index, + const int check_dupes) +{ + if (ctx->out_of_memory) + return; + + // Decide if this symbol is defined, and if it's in the current scope. + SymbolScope *item = NULL; + const void *value = NULL; + if ((check_dupes) && (sym != NULL) && (hash_find(map->hash, sym, &value))) + { + // check the current scope for a dupe. + // !!! FIXME: note current scope's starting index, see if found + // !!! FIXME: item is < index (and thus, a previous scope). + item = map->scope; + while ((item) && (item->symbol)) + { + if ( ((const void *) item) == value ) + { + failf(ctx, "Symbol '%s' already defined", sym); + return; + } // if + item = item->next; + } // while + } // if + + // Add the symbol to our map and scope stack. + item = (SymbolScope *) Malloc(ctx, sizeof (SymbolScope)); + if (item == NULL) + return; + + if (sym != NULL) // sym can be NULL if we're pushing a new scope. + { + if (hash_insert(map->hash, sym, item) == -1) + { + Free(ctx, item); + return; + } // if + } // if + + item->symbol = sym; // cached strings, don't copy. + item->index = index; + item->datatype = dt; + item->referenced = 0; + 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); + return; + } // 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, 0, 1); +} // push_usertype + +static inline void push_variable(Context *ctx, const char *sym, const MOJOSHADER_astDataType *dt) +{ + int idx = 0; + if (sym != NULL) + { + // leave space for individual member indexes. The IR will need this. + int additional = 0; + if (dt->type == MOJOSHADER_AST_DATATYPE_STRUCT) + additional = dt->structure.member_count; + if (ctx->is_func_scope) + { + idx = ++ctx->var_index; // these are positive. + ctx->var_index += additional; + } // if + else + { + idx = --ctx->global_var_index; // these are negative. + ctx->global_var_index -= additional; + } // else + } // if + + push_symbol(ctx, &ctx->variables, sym, dt, idx, 1); +} // push_variable + +static int push_function(Context *ctx, const char *sym, + const MOJOSHADER_astDataType *dt, + const int just_declare) +{ + // we don't have any reason to support nested functions at the moment, + // so this would be a bug. + assert(!ctx->is_func_scope); + assert(dt->type == MOJOSHADER_AST_DATATYPE_FUNCTION); + + // Functions are always global, so no need to search scopes. + // Functions overload, though, so we have to continue iterating to + // see if it matches anything. + const void *value = NULL; + void *iter = NULL; + while (hash_iter(ctx->variables.hash, sym, &value, &iter)) + { + // !!! FIXME: this breaks if you predeclare a function. + // !!! FIXME: (a declare AFTER defining works, though.) + // there's already something called this. + SymbolScope *item = (SymbolScope *) value; + if (datatypes_match(dt, item->datatype)) + { + if (!just_declare) + failf(ctx, "Function '%s' already defined.", sym); + return item->index; + } // if + } // while + + int idx = 0; + if ((sym != NULL) && (dt != NULL)) + { + if (!dt->function.intrinsic) + idx = ++ctx->user_func_index; // these are positive. + else + idx = --ctx->intrinsic_func_index; // these are negative. + } // if + + // push_symbol() doesn't check dupes, because we just did. + push_symbol(ctx, &ctx->variables, sym, dt, idx, 0); + + return idx; +} // push_function + +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, int *_index) +{ + const void *_item = NULL; + hash_find(map->hash, sym, &_item); + SymbolScope *item = (SymbolScope *) _item; + if (item != NULL) + { + item->referenced++; + if (_index != NULL) + *_index = item->index; + } // if + return item ? item->datatype : NULL; +} // find_symbol + +static inline const MOJOSHADER_astDataType *find_usertype(Context *ctx, const char *sym) +{ + return find_symbol(ctx, &ctx->usertypes, sym, NULL); +} // find_usertype + +static inline const MOJOSHADER_astDataType *find_variable(Context *ctx, const char *sym, int *_index) +{ + return find_symbol(ctx, &ctx->variables, sym, _index); +} // 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_identifier_expr(Context *ctx, + const char *string) +{ + NEW_AST_NODE(retval, MOJOSHADER_astExpressionIdentifier, + MOJOSHADER_AST_OP_IDENTIFIER); + retval->datatype = NULL; + retval->identifier = string; // cached; don't copy string. + retval->index = 0; + return (MOJOSHADER_astExpression *) retval; +} // new_identifier_expr + +static MOJOSHADER_astExpression *new_callfunc_expr(Context *ctx, + const char *identifier, + MOJOSHADER_astArguments *args) +{ + NEW_AST_NODE(retval, MOJOSHADER_astExpressionCallFunction, + MOJOSHADER_AST_OP_CALLFUNC); + MOJOSHADER_astExpression *expr = new_identifier_expr(ctx, identifier); + retval->datatype = NULL; + retval->identifier = (MOJOSHADER_astExpressionIdentifier *) expr; + 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->datatype = NULL; + 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->datatype = NULL; + 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->datatype = NULL; + retval->identifier = identifier; + retval->member = member; // cached; don't copy string. + retval->isswizzle = 0; // may change during semantic analysis. + retval->member_index = 0; // set during semantic analysis. + return (MOJOSHADER_astExpression *) retval; +} // new_deref_struct_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, (MOJOSHADER_astExpression*)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; + retval->index = 0; + 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->datatype = NULL; + 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->datatype = NULL; + 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 value ? ((SymbolScope *) value)->datatype : NULL; +} // 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 const MOJOSHADER_astDataType *reduce_datatype(Context *ctx, const MOJOSHADER_astDataType *dt) +{ + const MOJOSHADER_astDataType *retval = dt; + while (retval && retval->type == MOJOSHADER_AST_DATATYPE_USER) + { + // !!! FIXME: Ugh, const removal. + MOJOSHADER_astDataTypeUser *user = (MOJOSHADER_astDataTypeUser *) &retval->user; + if (user->details->type == MOJOSHADER_AST_DATATYPE_NONE) + { + // Take this opportunity to fix up some usertype stubs that were + // left over from the parse phase. You HAVE to catch these in the + // right scope, so be aggressive about calling reduce_datatype() + // as soon as things come into view! + user->details = get_usertype(ctx, user->name); + assert(user->details != NULL); + } // if + + retval = user->details; + } // while + + return retval; +} // reduce_datatype + + +static inline const MOJOSHADER_astDataType *sanitize_datatype(Context *ctx, const MOJOSHADER_astDataType *dt) +{ + reduce_datatype(ctx, dt); + return dt; +} // sanitize_datatype + + +static const MOJOSHADER_astDataType *build_function_datatype(Context *ctx, + const MOJOSHADER_astDataType *rettype, + const int paramcount, + const MOJOSHADER_astDataType **params, + const int intrinsic) +{ + if ( ((paramcount > 0) && (params == NULL)) || + ((paramcount == 0) && (params != NULL)) ) + return NULL; + + // !!! FIXME: this is hacky. + const MOJOSHADER_astDataType **dtparams; + void *ptr = Malloc(ctx, sizeof (*params) * paramcount); + if (ptr == NULL) + return NULL; + if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr))) + { + Free(ctx, ptr); + return NULL; + } // if + dtparams = (const MOJOSHADER_astDataType **) ptr; + memcpy(dtparams, params, sizeof (*params) * paramcount); + + 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; + dt->type = MOJOSHADER_AST_DATATYPE_FUNCTION; + dt->function.retval = rettype; + dt->function.params = dtparams; + dt->function.num_params = paramcount; + dt->function.intrinsic = intrinsic; + return dt; +} // build_function_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) ); + + sanitize_datatype(ctx, dt); + + // 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(ctx, datatype); + if (datatype->type == MOJOSHADER_AST_DATATYPE_VECTOR) + datatype = reduce_datatype(ctx, datatype->vector.base); + else if (datatype->type == MOJOSHADER_AST_DATATYPE_MATRIX) + datatype = reduce_datatype(ctx, datatype->matrix.base); + + 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(ctx, 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(ctx, 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(ctx, 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(ctx, datatype); + if (datatype->type == MOJOSHADER_AST_DATATYPE_STRUCT) + return; + + fail(ctx, "expected struct"); + // !!! FIXME: delete struct dereference for further processing. +} // require_struct_datatype + + +static int require_function_datatype(Context *ctx, + const MOJOSHADER_astDataType *datatype) +{ + datatype = reduce_datatype(ctx, datatype); + if ((!datatype) || (datatype->type != MOJOSHADER_AST_DATATYPE_FUNCTION)) + { + fail(ctx, "expected function"); + return 0; + } // if + + return 1; +} // 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(ctx, 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(ctx, _ldatatype); + const MOJOSHADER_astDataType *rdatatype = reduce_datatype(ctx, _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 lvector = 0; + int lmatrix = 0; + int l = STATICARRAYLEN(typeinf); + if (ldatatype != NULL) + { + MOJOSHADER_astDataTypeType type = ldatatype->type; + if (type == MOJOSHADER_AST_DATATYPE_VECTOR) + { + lvector = 1; + type = ldatatype->vector.base->type; + } // if + else if (type == MOJOSHADER_AST_DATATYPE_MATRIX) + { + lmatrix = 1; + type = ldatatype->matrix.base->type; + } // if + + for (l = 0; l < STATICARRAYLEN(typeinf); l++) + { + if (typeinf[l].type == type) + break; + } // for + } // if + + int rvector = 0; + int rmatrix = 0; + int r = STATICARRAYLEN(typeinf); + if (rdatatype != NULL) + { + MOJOSHADER_astDataTypeType type = rdatatype->type; + if (type == MOJOSHADER_AST_DATATYPE_VECTOR) + { + rvector = 1; + type = rdatatype->vector.base->type; + } // if + else if (type == MOJOSHADER_AST_DATATYPE_MATRIX) + { + rmatrix = 1; + type = rdatatype->matrix.base->type; + } // if + + for (r = 0; r < STATICARRAYLEN(typeinf); r++) + { + if (typeinf[r].type == 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 (lmatrix && !rmatrix) + choice = CHOOSE_LEFT; + else if (!lmatrix && rmatrix) + choice = CHOOSE_RIGHT; + else if (lvector && !rvector) + choice = CHOOSE_LEFT; + else if (!lvector && rvector) + choice = CHOOSE_RIGHT; + 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, const int veclen) +{ + int i; + int is_xyzw = 0; + int is_rgba = 0; + + assert(*str != '\0'); // can this actually happen? + + for (i = 0; i < veclen; 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 + +static int datatype_size(const MOJOSHADER_astDataType *dt) +{ + switch (dt->type) + { + case MOJOSHADER_AST_DATATYPE_BOOL: return 1; + case MOJOSHADER_AST_DATATYPE_INT: return 4; + case MOJOSHADER_AST_DATATYPE_UINT: return 4; + case MOJOSHADER_AST_DATATYPE_FLOAT: return 4; + case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM: return 4; + case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM: return 4; + case MOJOSHADER_AST_DATATYPE_HALF: return 2; + case MOJOSHADER_AST_DATATYPE_DOUBLE: return 8; + return 1; + default: + assert(0 && "Maybe should have used reduce_datatype()?"); + return 0; + } // switch +} // datatype_size + +static inline int is_scalar_datatype(const MOJOSHADER_astDataType *dt) +{ + switch (dt->type) + { + 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: + return 1; + default: + return 0; + } // switch +} // is_scalar_datatype + +static inline int is_float_datatype(const MOJOSHADER_astDataType *dt) +{ + switch (dt->type) + { + case MOJOSHADER_AST_DATATYPE_FLOAT: return 1; + case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM: return 1; + case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM: return 1; + default: return 0; + } // switch +} // is_float_datatype + +static int datatype_elems(Context *ctx, const MOJOSHADER_astDataType *dt) +{ + dt = reduce_datatype(ctx, dt); + switch (dt->type) + { + case MOJOSHADER_AST_DATATYPE_VECTOR: + return dt->vector.elements; + case MOJOSHADER_AST_DATATYPE_MATRIX: + return dt->matrix.rows * dt->matrix.columns; + default: + return 1; + } // switch +} // datatype_elems + +static const MOJOSHADER_astDataType *datatype_base(Context *ctx, const MOJOSHADER_astDataType *dt) +{ + dt = reduce_datatype(ctx, dt); + switch (dt->type) + { + case MOJOSHADER_AST_DATATYPE_VECTOR: + dt = dt->vector.base; + break; + case MOJOSHADER_AST_DATATYPE_MATRIX: + dt = dt->matrix.base; + break; + case MOJOSHADER_AST_DATATYPE_BUFFER: + dt = dt->buffer.base; + break; + case MOJOSHADER_AST_DATATYPE_ARRAY: + dt = dt->array.base; + break; + default: break; + } // switch + + return dt; +} // datatype_base + +typedef enum +{ + DT_MATCH_INCOMPATIBLE, // flatly incompatible + DT_MATCH_COMPATIBLE_DOWNCAST, // would have to lose precision + DT_MATCH_COMPATIBLE_UPCAST, // would have to gain precision + DT_MATCH_COMPATIBLE, // can cast to without serious change. + DT_MATCH_PERFECT // identical datatype. +} DatatypeMatch; + +static DatatypeMatch compatible_arg_datatype(Context *ctx, + const MOJOSHADER_astDataType *arg, + const MOJOSHADER_astDataType *param) +{ + // The matching rules for HLSL function overloading, as far as I can + // tell from experimenting with Microsoft's compiler, seem to be this: + // + // - All parameters of a function must match what the caller specified + // after possible type promotion via the following rules. + // - If the number of arguments and the number of parameters don't match, + // that overload is immediately rejected. + // - Each overloaded function is given a score that is the sum of the + // "worth" of each parameter vs the caller's arguments + // (see DatatypeMatch). The higher the score, the more favorable this + // function overload would be. + // - If there is a tie for highest score between two or more function + // overloads, we declare that function call to be ambiguous and fail(). + // - Scalars can be promoted to vectors to make a parameter match. + // - Scalars can promote to other scalars (short to int, etc). + // - Datatypes can downcast, but should generate a warning. + // (calling void fn(float x); as fn((double)1.0) should warn). + // - Vectors may NOT be extend (a float2 can't implicity extend to a + // float4). + // - Vectors with the same elements can promote (a half2 can become + // a float2). Downcasting between vectors with the same number of + // elements is allowed. + // - A perfect match of all params will be favored over any functions + // that only match if type promotion is applied (given a perfect match + // of all parameters, we'll stop looking for other matches). + + if (datatypes_match(arg, param)) + return DT_MATCH_PERFECT; // that was easy. + + arg = reduce_datatype(ctx, arg); + param = reduce_datatype(ctx, param); + + int do_base_test = 0; + + if (is_scalar_datatype(arg)) + do_base_test = 1; // we let these all go through for now. + + else if (arg->type == param->type) + { + if (arg->type == MOJOSHADER_AST_DATATYPE_VECTOR) + do_base_test = (arg->vector.elements == param->vector.elements); + else if (arg->type == MOJOSHADER_AST_DATATYPE_MATRIX) + { + do_base_test = + ((arg->matrix.rows == param->matrix.rows) && + (arg->matrix.columns == param->matrix.columns)); + } // if + } // if + + if (do_base_test) + { + arg = datatype_base(ctx, arg); + param = datatype_base(ctx, param); + + const int argsize = datatype_size(arg); + const int paramsize = datatype_size(param); + const int argfloat = is_float_datatype(arg); + const int paramfloat = is_float_datatype(param); + + if (argfloat && !paramfloat) + return DT_MATCH_COMPATIBLE_DOWNCAST; // always loss of precision. + else if (argfloat && !paramfloat) + { + if (argsize < paramsize) + return DT_MATCH_COMPATIBLE_UPCAST; + else + return DT_MATCH_COMPATIBLE_DOWNCAST; // loss of precision. + } // else if + else if (argsize == paramsize) + return DT_MATCH_COMPATIBLE; + else if (argsize < paramsize) + return DT_MATCH_COMPATIBLE_UPCAST; + else /* if (argsize > paramsize) */ + return DT_MATCH_COMPATIBLE_DOWNCAST; + } // if + + return DT_MATCH_INCOMPATIBLE; +} // compatible_arg_datatype + + +static const MOJOSHADER_astDataType *type_check_ast(Context *ctx, void *_ast); + +// !!! FIXME: this function sucks. +static const MOJOSHADER_astDataType *match_func_to_call(Context *ctx, + MOJOSHADER_astExpressionCallFunction *ast) +{ + SymbolScope *best = NULL; // best choice we find. + int best_score = 0; + MOJOSHADER_astExpressionIdentifier *ident = ast->identifier; + const char *sym = ident->identifier; + const void *value = NULL; + void *iter = NULL; + + int argcount = 0; + MOJOSHADER_astArguments *args = ast->args; + while (args != NULL) + { + argcount++; + type_check_ast(ctx, args->argument); + args = args->next; + } // while; + + // we do some tapdancing to handle function overloading here. + int match = 0; + while (hash_iter(ctx->variables.hash, sym, &value, &iter)) + { + SymbolScope *item = (SymbolScope *) value; + const MOJOSHADER_astDataType *dt = item->datatype; + dt = reduce_datatype(ctx, dt); + // there's a locally-scoped symbol with this name? It takes precedence. + if (dt->type != MOJOSHADER_AST_DATATYPE_FUNCTION) + return dt; + + const MOJOSHADER_astDataTypeFunction *dtfn = (MOJOSHADER_astDataTypeFunction *) dt; + const int perfect = argcount * ((int) DT_MATCH_PERFECT); + int score = 0; + + if (argcount == dtfn->num_params) // !!! FIXME: default args. + { + args = ast->args; + int i; + for (i = 0; i < argcount; i++) + { + assert(args != NULL); + dt = args->argument->datatype; + args = args->next; + const DatatypeMatch compatible = compatible_arg_datatype(ctx, dt, dtfn->params[i]); + if (compatible == DT_MATCH_INCOMPATIBLE) + { + args = NULL; + score = 0; + break; + } // if + + score += (int) compatible; + } // for + + if (args != NULL) + score = 0; // too many arguments supplied. No match. + } // else + + if (score == 0) // incompatible. + continue; + + else if (score == perfect) // perfection! stop looking! + { + match = 1; // ignore all other compatible matches. + best = item; + break; + } // if + + else if (score >= best_score) // compatible, but not perfect, match. + { + if (score == best_score) + { + match++; + // !!! FIXME: list each possible function in a fail(), + // !!! FIXME: but you can't actually fail() here, since + // !!! FIXME: this may cease to be ambiguous if we get + // !!! FIXME: a better match on a later overload. + } // if + + else if (score > best_score) + { + match = 1; // reset the ambiguousness count. + best = item; + best_score = score; + } // if + } // else if + } // while + + if (match > 1) + { + assert(best != NULL); + failf(ctx, "Ambiguous function call to '%s'", sym); + } // if + + if (best == NULL) + { + assert(match == 0); + assert(best_score == 0); + // !!! FIXME: ident->datatype = ? + failf(ctx, "No matching function named '%s'", sym); + } // if + else + { + ident->datatype = reduce_datatype(ctx, best->datatype); + ident->index = best->index; + } // else + + return ident->datatype; +} // match_func_to_call + + +static const MOJOSHADER_astDataType *vectype_from_base(Context *ctx, + const MOJOSHADER_astDataType *base, + const int len) +{ + assert(len > 0); + assert(len <= 4); + + if (len == 1) // return "float" and not "float1" + return base; + + const char *typestr = NULL; + switch (base->type) + { + case MOJOSHADER_AST_DATATYPE_BOOL: typestr = "bool"; break; + case MOJOSHADER_AST_DATATYPE_INT: typestr = "int"; break; + case MOJOSHADER_AST_DATATYPE_UINT: typestr = "uint"; break; + case MOJOSHADER_AST_DATATYPE_HALF: typestr = "half"; break; + case MOJOSHADER_AST_DATATYPE_FLOAT: typestr = "float"; break; + case MOJOSHADER_AST_DATATYPE_DOUBLE: typestr = "double"; break; + default: assert(0 && "This shouldn't happen"); break; + } // switch + + char buf[32]; + snprintf(buf, sizeof (buf), "%s%d", typestr, len); + const MOJOSHADER_astDataType *datatype = get_usertype(ctx, buf); + assert(datatype != NULL); + return datatype; +} // vectype_from_base + + +// 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: + // !!! FIXME: must be lvalue. + // !!! FIXME: bools must type-promote to ...int? + // !!! FIXME: complement must not be float (...right?) + datatype = type_check_ast(ctx, ast->unary.operand); + require_numeric_datatype(ctx, datatype); + ast->unary.datatype = datatype; + return datatype; + + case MOJOSHADER_AST_OP_NOT: + datatype = type_check_ast(ctx, ast->unary.operand); + require_boolean_datatype(ctx, datatype); + // !!! FIXME: coerce to bool here. + ast->unary.datatype = &ctx->dt_bool; + 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_integer_datatype(ctx, datatype2); + add_type_coercion(ctx, NULL, &ctx->dt_int, &ast->binary.right, datatype2); + + datatype = reduce_datatype(ctx, datatype); + if (datatype->type == MOJOSHADER_AST_DATATYPE_VECTOR) + { + // !!! FIXME: if constant int, fail if not 0 >= value <= vecsize. + ast->binary.datatype = datatype->vector.base; + } // if + else if (datatype->type == MOJOSHADER_AST_DATATYPE_MATRIX) + { + // !!! FIXME: if constant int, fail if not 0 >= value <= rowsize (colsize?). + ast->binary.datatype = vectype_from_base(ctx, datatype->matrix.base, datatype->matrix.columns); // !!! FIXME: rows? + } + else + { + require_array_datatype(ctx, datatype); + ast->binary.datatype = array_element_datatype(ctx, datatype); + } // else + + 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(ctx, datatype); + + // Is this a swizzle and not a struct deref? + if (reduced->type == MOJOSHADER_AST_DATATYPE_VECTOR) + { + const int veclen = reduced->vector.elements; + ast->derefstruct.isswizzle = 1; + if (!is_swizzle_str(member, veclen)) + { + fail(ctx, "invalid swizzle on vector"); + // force this to be sane for further processing. + const char *sane_swiz = stringcache(ctx->strcache, "xyzw"); + member = ast->derefstruct.member = sane_swiz; + } // if + + const int swizlen = (int) strlen(member); + if (swizlen != veclen) + datatype = vectype_from_base(ctx, reduced->vector.base, swizlen); + + ast->derefstruct.datatype = datatype; + 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 = reduced->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; + ast->derefstruct.member_index = i; + 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); + // !!! FIXME: coerce each to bool here, separately. + 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: + // !!! FIXME: verify binary.left is an lvalue, or fail()! + 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, &ast->identifier.index); + 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 = match_func_to_call(ctx, &ast->callfunc); + const MOJOSHADER_astDataType *reduced = reduce_datatype(ctx, datatype); + // !!! FIXME: replace AST node with an int if this isn't a func. + if (!require_function_datatype(ctx, reduced)) + { + ast->callfunc.datatype = &ctx->dt_int; + return ast->callfunc.datatype; + } // if + + MOJOSHADER_astArguments *arg = ast->callfunc.args; + int i; + for (i = 0; i < reduced->function.num_params; i++) + { + if (arg == NULL) // !!! FIXME: check for default parameters, fill them in. + { + fail(ctx, "Too few arguments"); + // !!! FIXME: replace AST here. + break; + } // if + datatype2 = arg->argument->datatype; // already type-checked. + add_type_coercion(ctx, NULL, reduced->function.params[i], + &arg->argument, datatype2); + arg = arg->next; + } // for + + assert(arg == NULL); // shouldn't have chosen func if too many args. + + ast->callfunc.datatype = reduced->function.retval; + return ast->callfunc.datatype; + } // case + + case MOJOSHADER_AST_OP_CONSTRUCTOR: + { + const MOJOSHADER_astDataType *reduced = reduce_datatype(ctx, 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); + + // "float4(float3(1,2,3),4)" is legal, so we need to see if + // we're a vector, and jump that number of parameters instead + // of doing type coercion. + reduced = reduce_datatype(ctx, datatype2); + if (reduced->type == MOJOSHADER_AST_DATATYPE_VECTOR) + { + // make sure things like float4(half3(1,2,3),1) convert that half3 to float3. + const int count = reduced->vector.elements; + datatype3 = vectype_from_base(ctx, base_dt, count); + add_type_coercion(ctx, NULL, datatype3, &arg->argument, datatype2); + i += count - 1; + } // else + else + { + add_type_coercion(ctx, NULL, base_dt, &arg->argument, datatype2); + } // else + 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 = sanitize_datatype(ctx, 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: + if ((ctx->loop_count == 0) && (ctx->switch_count == 0)) + fail(ctx, "Break outside loop or switch"); + // !!! FIXME: warn if unreachable statements follow? + type_check_ast(ctx, ast->stmt.next); + return NULL; + + case MOJOSHADER_AST_STATEMENT_CONTINUE: + if (ctx->loop_count == 0) + fail(ctx, "Continue outside loop"); + // !!! FIXME: warn if unreachable statements follow? + type_check_ast(ctx, ast->stmt.next); + return NULL; + + case MOJOSHADER_AST_STATEMENT_DISCARD: + // !!! FIXME: warn if unreachable statements follow? + type_check_ast(ctx, ast->stmt.next); + return NULL; + + 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: + { + ctx->switch_count++; + MOJOSHADER_astSwitchCases *cases = ast->switchstmt.cases; + // !!! FIXME: expr must be POD (no structs, arrays, etc!). + datatype = type_check_ast(ctx, ast->switchstmt.expr); + while (cases) + { + // !!! FIXME: case must be POD (no structs, arrays, etc!). + 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 + ctx->switch_count--; + type_check_ast(ctx, ast->switchstmt.next); + 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: + ctx->loop_count++; + 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); + ctx->loop_count--; + type_check_ast(ctx, ast->forstmt.next); + return NULL; + + case MOJOSHADER_AST_STATEMENT_DO: + ctx->loop_count++; + // !!! FIXME: should there be a push_scope() here? + 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); + ctx->loop_count--; + type_check_ast(ctx, ast->dostmt.next); + return NULL; + + case MOJOSHADER_AST_STATEMENT_WHILE: + ctx->loop_count++; + 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); + ctx->loop_count--; + type_check_ast(ctx, ast->whilestmt.next); + return NULL; + + case MOJOSHADER_AST_STATEMENT_RETURN: + // !!! FIXME: type coercion to outer function's return type. + // !!! FIXME: warn if unreachable statements follow? + type_check_ast(ctx, ast->returnstmt.expr); + type_check_ast(ctx, ast->returnstmt.next); + return NULL; + + case MOJOSHADER_AST_COMPUNIT_FUNCTION: + assert(!ctx->is_func_scope); + + // We have to tapdance here to make sure the function is in + // the global scope, but it's parameters are pushed as variables + // in the function's scope. + datatype = type_check_ast(ctx, ast->funcunit.declaration); + ast->funcunit.index = push_function(ctx, + ast->funcunit.declaration->identifier, + datatype, ast->funcunit.definition == NULL); + + // not just a declaration, but a full function definition? + if (ast->funcunit.definition != NULL) + { + assert(ctx->loop_count == 0); + assert(ctx->switch_count == 0); + ctx->is_func_scope = 1; + ctx->var_index = 0; // reset this every function. + push_scope(ctx); // so function params are in function scope. + // repush the parameters before checking the actual function. + MOJOSHADER_astFunctionParameters *param; + for (param = ast->funcunit.declaration->params; param; param = param->next) + push_variable(ctx, param->identifier, param->datatype); + type_check_ast(ctx, ast->funcunit.definition); + pop_scope(ctx); + ctx->is_func_scope = 0; + assert(ctx->loop_count == 0); + assert(ctx->switch_count == 0); + } // 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; + const MOJOSHADER_astDataType *dtparams[64]; + + int i = 0; + for (param = ast->funcsig.params; param; param = param->next) + { + assert(i <= STATICARRAYLEN(dtparams)); // laziness. + sanitize_datatype(ctx, param->datatype); + if (param->initializer != NULL) + { + datatype2 = type_check_ast(ctx, param->initializer); + add_type_coercion(ctx, NULL, param->datatype, + ¶m->initializer, datatype2); + } // if + dtparams[i] = param->datatype; + i++; + } // for + + ast->funcsig.datatype = build_function_datatype(ctx, + ast->funcsig.datatype, + i, dtparams, 0); + return ast->funcsig.datatype; + } // case + + case MOJOSHADER_AST_STRUCT_DECLARATION: + { + // !!! FIXME: We don't handle struct predeclaration at all right now + // !!! FIXME: (neither does the grammar)...not only does that mean + // !!! FIXME: you need to know the struct definition up front, but + // !!! FIXME: you can't do "struct XXX *next;" for a self-referencing + // !!! FIXME: linked list struct thing. This probably isn't a big + // !!! FIXME: deal, as there aren't (CURRENTLY!) pointers in HLSL, + // !!! FIXME: but you never know. + + 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; + + // !!! FIXME: this shouldn't push for anonymous structs: "struct { int x; } myvar;" + // !!! FIXME: but right now, the grammar is wrong and requires a name for the struct. + 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: + { + MOJOSHADER_astVariableDeclaration *decl = &ast->vardecl; + + // this is true now, but we'll fill in ->datatype no matter what. + assert((decl->datatype && !decl->anonymous_datatype) || + (!decl->datatype && decl->anonymous_datatype)); + + // An anonymous struct? That AST node does the heavy lifting. + if (decl->anonymous_datatype != NULL) + datatype = type_check_ast(ctx, decl->anonymous_datatype); + else + { + datatype = build_datatype(ctx, (decl->attributes & MOJOSHADER_AST_VARATTR_CONST) != 0, + decl->datatype, decl->details); + } // else + + while (decl != NULL) + { + decl->datatype = datatype; + push_variable(ctx, decl->details->identifier, datatype); + if (decl->initializer != NULL) + { + datatype2 = type_check_ast(ctx, decl->initializer); + add_type_coercion(ctx, NULL, datatype, &decl->initializer, datatype2); + } // if + + type_check_ast(ctx, decl->annotations); + type_check_ast(ctx, decl->lowlevel); + decl = decl->next; + } // while + + return datatype; + } // case + + 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); +} // 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 delete_ir(Context *ctx, void *_ir); // !!! FIXME: move this code around. + +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; + size_t i = 0; + + // !!! 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) + { + 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); + + if (ctx->ir != NULL) + { + for (i = 0; i <= ctx->user_func_index; i++) + delete_ir(ctx, ctx->ir[i]); + f(ctx->ir, d); + } // if + + // !!! 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_none.type = MOJOSHADER_AST_DATATYPE_NONE; + 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 + + +// This macro salsa is kinda nasty, but it's the smallest, least error-prone +// way I can find to do this well in C. :/ + +#define ADD_INTRINSIC(fn, ret, params) do { \ + push_function(ctx, fn, \ + build_function_datatype(ctx, ret, STATICARRAYLEN(params), params, 1), \ + 0); \ +} while (0) + +#define ADD_INTRINSIC_VECTOR(typestr, code) do { \ + const MOJOSHADER_astDataType *dt; \ + dt = get_usertype(ctx, typestr "1"); code; \ + dt = get_usertype(ctx, typestr "2"); code; \ + dt = get_usertype(ctx, typestr "3"); code; \ + dt = get_usertype(ctx, typestr "4"); code; \ +} while (0) + +#define ADD_INTRINSIC_VECTOR_FLOAT(code) { \ + ADD_INTRINSIC_VECTOR("float", code); \ + ADD_INTRINSIC_VECTOR("half", code); \ + ADD_INTRINSIC_VECTOR("double", code); \ +} +#define ADD_INTRINSIC_VECTOR_INT(code) { \ + ADD_INTRINSIC_VECTOR("int", code); \ + ADD_INTRINSIC_VECTOR("uint", code); \ +} +#define ADD_INTRINSIC_VECTOR_BOOL(code) { \ + ADD_INTRINSIC_VECTOR("bool", code); \ +} + +#define ADD_INTRINSIC_MATRIX(typestr, code) do { \ + const MOJOSHADER_astDataType *dt; \ + dt = get_usertype(ctx, typestr "1x1"); code; \ + dt = get_usertype(ctx, typestr "1x2"); code; \ + dt = get_usertype(ctx, typestr "1x3"); code; \ + dt = get_usertype(ctx, typestr "1x4"); code; \ + dt = get_usertype(ctx, typestr "2x1"); code; \ + dt = get_usertype(ctx, typestr "2x2"); code; \ + dt = get_usertype(ctx, typestr "2x3"); code; \ + dt = get_usertype(ctx, typestr "2x4"); code; \ + dt = get_usertype(ctx, typestr "3x1"); code; \ + dt = get_usertype(ctx, typestr "3x2"); code; \ + dt = get_usertype(ctx, typestr "3x3"); code; \ + dt = get_usertype(ctx, typestr "3x4"); code; \ + dt = get_usertype(ctx, typestr "4x1"); code; \ + dt = get_usertype(ctx, typestr "4x2"); code; \ + dt = get_usertype(ctx, typestr "4x3"); code; \ + dt = get_usertype(ctx, typestr "4x4"); code; \ +} while (0) + +#define ADD_INTRINSIC_MATRIX_FLOAT(code) { \ + ADD_INTRINSIC_MATRIX("float", code); \ + ADD_INTRINSIC_MATRIX("half", code); \ + ADD_INTRINSIC_MATRIX("double", code); \ +} +#define ADD_INTRINSIC_MATRIX_INT(code) { \ + ADD_INTRINSIC_MATRIX("int", code); \ + ADD_INTRINSIC_MATRIX("uint", code); \ +} +#define ADD_INTRINSIC_MATRIX_BOOL(code) { \ + ADD_INTRINSIC_MATRIX("bool", code); \ +} + +#define ADD_INTRINSIC_ANY(scalar, typestr, code) do { \ + { const MOJOSHADER_astDataType *dt = scalar; code; } \ + ADD_INTRINSIC_VECTOR(typestr, code); \ + ADD_INTRINSIC_MATRIX(typestr, code); \ +} while (0) + +#define ADD_INTRINSIC_ANY_FLOAT(code) do { \ + ADD_INTRINSIC_ANY(&ctx->dt_double, "double", code); \ + ADD_INTRINSIC_ANY(&ctx->dt_half, "half", code); \ + ADD_INTRINSIC_ANY(&ctx->dt_float, "float", code); \ +} while (0) +#define ADD_INTRINSIC_ANY_INT(code) do { \ + ADD_INTRINSIC_ANY(&ctx->dt_uint, "uint", code); \ + ADD_INTRINSIC_ANY(&ctx->dt_int, "int", code); \ +} while (0) + +#define ADD_INTRINSIC_ANY_BOOL(code) ADD_INTRINSIC_ANY(&ctx->dt_bool, "bool", code) + +static void add_intrinsic1(Context *ctx, const char *fn, + const MOJOSHADER_astDataType *ret, + const MOJOSHADER_astDataType *dt1) +{ + const MOJOSHADER_astDataType *params[] = { dt1 }; + ADD_INTRINSIC(fn, ret, params); +} // add_intrinsic1 + +static void add_intrinsic2(Context *ctx, const char *fn, + const MOJOSHADER_astDataType *ret, + const MOJOSHADER_astDataType *dt1, + const MOJOSHADER_astDataType *dt2) +{ + const MOJOSHADER_astDataType *params[] = { dt1, dt2 }; + ADD_INTRINSIC(fn, ret, params); +} // add_intrinsic2 + +static void add_intrinsic3(Context *ctx, const char *fn, + const MOJOSHADER_astDataType *ret, + const MOJOSHADER_astDataType *dt1, + const MOJOSHADER_astDataType *dt2, + const MOJOSHADER_astDataType *dt3) +{ + const MOJOSHADER_astDataType *params[] = { dt1, dt2, dt3 }; + ADD_INTRINSIC(fn, ret, params); +} // add_intrinsic3 + +static void add_intrinsic4(Context *ctx, const char *fn, + const MOJOSHADER_astDataType *ret, + const MOJOSHADER_astDataType *dt1, + const MOJOSHADER_astDataType *dt2, + const MOJOSHADER_astDataType *dt3, + const MOJOSHADER_astDataType *dt4) +{ + const MOJOSHADER_astDataType *params[] = { dt1, dt2, dt3, dt4 }; + ADD_INTRINSIC(fn, ret, params); +} // add_intrinsic4 + +// PLEASE NOTE that add_intrinsic*() is called AFTER the various +// ADD_INTRINSIC_* macros, even though these look like functions that +// should be called first. They might be called multiple times by the macro. +// The variable "dt" is defined by the macro for use by your code. +static void add_intrinsic_SAME1_ANYf(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_FLOAT(add_intrinsic1(ctx, fn, dt, dt)); +} // add_intrinsic_SAME1_ANYf + +static void add_intrinsic_SAME1_ANYfi(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_INT(add_intrinsic1(ctx, fn, dt, dt)); + add_intrinsic_SAME1_ANYf(ctx, fn); +} // add_intrinsic_SAME1_ANYfi + +static void add_intrinsic_BOOL_ANYf(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_FLOAT(add_intrinsic1(ctx, fn, &ctx->dt_bool, dt)); +} // add_intrinsic_BOOL_ANYf + +static void add_intrinsic_BOOL_ANYfib(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_BOOL(add_intrinsic1(ctx, fn, &ctx->dt_bool, dt)); + ADD_INTRINSIC_ANY_INT(add_intrinsic1(ctx, fn, &ctx->dt_bool, dt)); + add_intrinsic_BOOL_ANYf(ctx, fn); +} // add_intrinsic_BOOL_ANYfib + +static void add_intrinsic_SAME1_ANYf_SAME1(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_FLOAT(add_intrinsic2(ctx, fn, dt, dt, dt)); +} // add_intrinsic_SAME1_ANYf_SAME1 + +static void add_intrinsic_SAME1_ANYfi_SAME1(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_INT(add_intrinsic2(ctx, fn, dt, dt, dt)); + add_intrinsic_SAME1_ANYf_SAME1(ctx, fn); +} // add_intrinsic_SAME1_ANYfi_SAME1 + +static void add_intrinsic_SAME1_ANYf_SAME1_SAME1(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_FLOAT(add_intrinsic3(ctx, fn, dt, dt, dt, dt)); +} // add_intrinsic_SAME1_ANYf_SAME1_SAME1 + +static void add_intrinsic_SAME1_ANYfi_SAME1_SAME1(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_INT(add_intrinsic3(ctx, fn, dt, dt, dt, dt)); + add_intrinsic_SAME1_ANYf_SAME1_SAME1(ctx, fn); +} // add_intrinsic_SAME1_ANYfi_SAME1_SAME1 + +static void add_intrinsic_SAME1_Mfib(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_MATRIX_BOOL(add_intrinsic1(ctx, fn, dt, dt)); + ADD_INTRINSIC_MATRIX_INT(add_intrinsic1(ctx, fn, dt, dt)); + ADD_INTRINSIC_MATRIX_FLOAT(add_intrinsic1(ctx, fn, dt, dt)); +} // add_intrinsic_SAME1_Mfib + +static void add_intrinsic_SAME1_Vf(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic1(ctx, fn, dt, dt)); +} // add_intrinsic_SAME1_Vf + +static void add_intrinsic_SAME1_Vf_SAME1_SAME1(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic3(ctx, fn, dt, dt, dt, dt)); +} // add_intrinsic_SAME1_Vf_SAME1_SAME1 + +static void add_intrinsic_SAME1_Vf_SAME1_f(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic3(ctx, fn, dt, dt, dt, dt->user.details->vector.base)); +} // add_intrinsic_SAME1_Vf_SAME1_f + +static void add_intrinsic_VOID_ANYf(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_FLOAT(add_intrinsic1(ctx, fn, NULL, dt)); +} // add_intrinsic_VOID_ANYf + +static void add_intrinsic_VOID_ANYf_SAME1_SAME1(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_ANY_FLOAT(add_intrinsic3(ctx, fn, NULL, dt, dt, dt)); +} // add_intrinsic_VOID_ANYf_SAME1_SAME1 + +static void add_intrinsic_f_SQUAREMATRIXf(Context *ctx, const char *fn) +{ + add_intrinsic1(ctx, fn, &ctx->dt_float, get_usertype(ctx, "float1x1")); + add_intrinsic1(ctx, fn, &ctx->dt_float, get_usertype(ctx, "float2x2")); + add_intrinsic1(ctx, fn, &ctx->dt_float, get_usertype(ctx, "float3x3")); + add_intrinsic1(ctx, fn, &ctx->dt_float, get_usertype(ctx, "float4x4")); +} // add_intrinsic_f_SQUAREMATRIXf + +static void add_intrinsic_f_Vf(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic1(ctx, fn, dt->user.details->vector.base, dt)); +} // add_intrinsic_f_Vf + +static void add_intrinsic_fi_Vfi_SAME1(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_VECTOR_INT(add_intrinsic2(ctx, fn, dt->user.details->vector.base, dt, dt)); + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dt->user.details->vector.base, dt, dt)); +} // add_intrinsic_fi_Vfi_SAME1 + +static void add_intrinsic_f_Vf_SAME1(Context *ctx, const char *fn) +{ + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dt->user.details->vector.base, dt, dt)); +} // add_intrinsic_f_Vf_SAME1 + +static void add_intrinsic_3f_3f_3f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *dt = get_usertype(ctx, "float3"); + add_intrinsic2(ctx, fn, dt, dt, dt); +} // add_intrinsic_3f_3f_3f + +static void add_intrinsic_4f_f_f_f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *f = &ctx->dt_float; + add_intrinsic3(ctx, fn, f4, f, f, f); +} // add_intrinsic_4f_f_f_f + +static void add_intrinsic_4f_s1_4f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *dt = get_usertype(ctx, "float4"); + add_intrinsic2(ctx, fn, dt, &ctx->dt_sampler1d, dt); +} // add_intrinsic_4f_s1_4f + +static void add_intrinsic_4f_s1_f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *dt = get_usertype(ctx, "float4"); + add_intrinsic2(ctx, fn, dt, &ctx->dt_sampler1d, &ctx->dt_float); +} // add_intrinsic_4f_s1_f + +static void add_intrinsic_4f_s1_f_f_f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *dt = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *f = &ctx->dt_float; + add_intrinsic4(ctx, fn, dt, &ctx->dt_sampler1d, f, f, f); +} // add_intrinsic_4f_s1_f_f_f + +static void add_intrinsic_4f_s2_2f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *f2 = get_usertype(ctx, "float2"); + add_intrinsic2(ctx, fn, f4, &ctx->dt_sampler2d, f2); +} // add_intrinsic_4f_s2_2f + +static void add_intrinsic_4f_s2_2f_2f_2f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *f2 = get_usertype(ctx, "float2"); + add_intrinsic4(ctx, fn, f4, &ctx->dt_sampler2d, f2, f2, f2); +} // add_intrinsic_4f_s2_2f_2f_2f + +static void add_intrinsic_4f_s2_4f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + add_intrinsic2(ctx, fn, f4, &ctx->dt_sampler2d, f4); +} // add_intrinsic_4f_s2_4f + +static void add_intrinsic_4f_s3_3f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3"); + add_intrinsic2(ctx, fn, f4, &ctx->dt_sampler3d, f3); +} // add_intrinsic_4f_s3_3f + +static void add_intrinsic_4f_s3_3f_3f_3f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3"); + add_intrinsic4(ctx, fn, f4, &ctx->dt_sampler3d, f3, f3, f3); +} // add_intrinsic_4f_s3_3f_3f_3f + +static void add_intrinsic_4f_s3_4f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + add_intrinsic2(ctx, fn, f4, &ctx->dt_sampler3d, f4); +} // add_intrinsic_4f_s3_4f + +static void add_intrinsic_4f_sc_3f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3"); + add_intrinsic2(ctx, fn, f4, &ctx->dt_samplercube, f3); +} // add_intrinsic_4f_sc_3f + +static void add_intrinsic_4f_sc_3f_3f_3f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3"); + add_intrinsic4(ctx, fn, f4, &ctx->dt_samplercube, f3, f3, f3); +} // add_intrinsic_4f_sc_3f_3f_3f + +static void add_intrinsic_4f_sc_4f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + add_intrinsic2(ctx, fn, f4, &ctx->dt_samplercube, f4); +} // add_intrinsic_4f_sc_4f + +static void add_intrinsic_4i_4f(Context *ctx, const char *fn) +{ + const MOJOSHADER_astDataType *i4 = get_usertype(ctx, "int4"); + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + add_intrinsic1(ctx, fn, i4, f4); +} // add_intrinsic_4i_4f + +static void add_intrinsic_mul(Context *ctx, const char *fn) +{ + // mul() is nasty, since there's a bunch of overloads that aren't just + // related to vector size. + // !!! FIXME: needs half, double, uint... + const MOJOSHADER_astDataType *dtf = &ctx->dt_float; + const MOJOSHADER_astDataType *dti = &ctx->dt_int; + const MOJOSHADER_astDataType *f1 = get_usertype(ctx, "float1"); + const MOJOSHADER_astDataType *f2 = get_usertype(ctx, "float2"); + const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3"); + const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4"); + const MOJOSHADER_astDataType *i1 = get_usertype(ctx, "int1"); + const MOJOSHADER_astDataType *i2 = get_usertype(ctx, "int2"); + const MOJOSHADER_astDataType *i3 = get_usertype(ctx, "int3"); + const MOJOSHADER_astDataType *i4 = get_usertype(ctx, "int4"); + const MOJOSHADER_astDataType *f1x1 = get_usertype(ctx, "float1x1"); + const MOJOSHADER_astDataType *f1x2 = get_usertype(ctx, "float1x2"); + const MOJOSHADER_astDataType *f1x3 = get_usertype(ctx, "float1x3"); + const MOJOSHADER_astDataType *f1x4 = get_usertype(ctx, "float1x4"); + const MOJOSHADER_astDataType *f2x1 = get_usertype(ctx, "float2x1"); + const MOJOSHADER_astDataType *f2x2 = get_usertype(ctx, "float2x2"); + const MOJOSHADER_astDataType *f2x3 = get_usertype(ctx, "float2x3"); + const MOJOSHADER_astDataType *f2x4 = get_usertype(ctx, "float2x4"); + const MOJOSHADER_astDataType *f3x1 = get_usertype(ctx, "float3x1"); + const MOJOSHADER_astDataType *f3x2 = get_usertype(ctx, "float3x2"); + const MOJOSHADER_astDataType *f3x3 = get_usertype(ctx, "float3x3"); + const MOJOSHADER_astDataType *f3x4 = get_usertype(ctx, "float3x4"); + const MOJOSHADER_astDataType *f4x1 = get_usertype(ctx, "float4x1"); + const MOJOSHADER_astDataType *f4x2 = get_usertype(ctx, "float4x2"); + const MOJOSHADER_astDataType *f4x3 = get_usertype(ctx, "float4x3"); + const MOJOSHADER_astDataType *f4x4 = get_usertype(ctx, "float4x4"); + const MOJOSHADER_astDataType *i1x1 = get_usertype(ctx, "int1x1"); + const MOJOSHADER_astDataType *i1x2 = get_usertype(ctx, "int1x2"); + const MOJOSHADER_astDataType *i1x3 = get_usertype(ctx, "int1x3"); + const MOJOSHADER_astDataType *i1x4 = get_usertype(ctx, "int1x4"); + const MOJOSHADER_astDataType *i2x1 = get_usertype(ctx, "int2x1"); + const MOJOSHADER_astDataType *i2x2 = get_usertype(ctx, "int2x2"); + const MOJOSHADER_astDataType *i2x3 = get_usertype(ctx, "int2x3"); + const MOJOSHADER_astDataType *i2x4 = get_usertype(ctx, "int2x4"); + const MOJOSHADER_astDataType *i3x1 = get_usertype(ctx, "int3x1"); + const MOJOSHADER_astDataType *i3x2 = get_usertype(ctx, "int3x2"); + const MOJOSHADER_astDataType *i3x3 = get_usertype(ctx, "int3x3"); + const MOJOSHADER_astDataType *i3x4 = get_usertype(ctx, "int3x4"); + const MOJOSHADER_astDataType *i4x1 = get_usertype(ctx, "int4x1"); + const MOJOSHADER_astDataType *i4x2 = get_usertype(ctx, "int4x2"); + const MOJOSHADER_astDataType *i4x3 = get_usertype(ctx, "int4x3"); + const MOJOSHADER_astDataType *i4x4 = get_usertype(ctx, "int4x4"); + + // scalar * scalar + add_intrinsic2(ctx, fn, dti, dti, dti); + add_intrinsic2(ctx, fn, dtf, dtf, dtf); + + // scalar * vector + ADD_INTRINSIC_VECTOR_INT(add_intrinsic2(ctx, fn, dt, dti, dt)); + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dt, dtf, dt)); + + // scalar * matrix + ADD_INTRINSIC_MATRIX_INT(add_intrinsic2(ctx, fn, dt, dti, dt)); + ADD_INTRINSIC_MATRIX_FLOAT(add_intrinsic2(ctx, fn, dt, dtf, dt)); + + // vector * scalar + ADD_INTRINSIC_VECTOR_INT(add_intrinsic2(ctx, fn, dt, dt, dti)); + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dt, dt, dtf)); + + // vector * vector + ADD_INTRINSIC_VECTOR_INT(add_intrinsic2(ctx, fn, dti, dt, dt)); + ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dtf, dt, dt)); + + // vector * matrix + add_intrinsic2(ctx, fn, i1, i1, i1x1); + add_intrinsic2(ctx, fn, i2, i1, i1x2); + add_intrinsic2(ctx, fn, i3, i1, i1x3); + add_intrinsic2(ctx, fn, i4, i1, i1x4); + add_intrinsic2(ctx, fn, i1, i2, i2x1); + add_intrinsic2(ctx, fn, i2, i2, i2x2); + add_intrinsic2(ctx, fn, i3, i2, i2x3); + add_intrinsic2(ctx, fn, i4, i2, i2x4); + add_intrinsic2(ctx, fn, i1, i3, i3x1); + add_intrinsic2(ctx, fn, i2, i3, i3x2); + add_intrinsic2(ctx, fn, i3, i3, i3x3); + add_intrinsic2(ctx, fn, i4, i3, i3x4); + add_intrinsic2(ctx, fn, i1, i4, i4x1); + add_intrinsic2(ctx, fn, i2, i4, i4x2); + add_intrinsic2(ctx, fn, i3, i4, i4x3); + add_intrinsic2(ctx, fn, i4, i4, i4x4); + add_intrinsic2(ctx, fn, f1, f1, f1x1); + add_intrinsic2(ctx, fn, f2, f1, f1x2); + add_intrinsic2(ctx, fn, f3, f1, f1x3); + add_intrinsic2(ctx, fn, f4, f1, f1x4); + add_intrinsic2(ctx, fn, f1, f2, f2x1); + add_intrinsic2(ctx, fn, f2, f2, f2x2); + add_intrinsic2(ctx, fn, f3, f2, f2x3); + add_intrinsic2(ctx, fn, f4, f2, f2x4); + add_intrinsic2(ctx, fn, f1, f3, f3x1); + add_intrinsic2(ctx, fn, f2, f3, f3x2); + add_intrinsic2(ctx, fn, f3, f3, f3x3); + add_intrinsic2(ctx, fn, f4, f3, f3x4); + add_intrinsic2(ctx, fn, f1, f4, f4x1); + add_intrinsic2(ctx, fn, f2, f4, f4x2); + add_intrinsic2(ctx, fn, f3, f4, f4x3); + add_intrinsic2(ctx, fn, f4, f4, f4x4); + + // matrix * scalar + ADD_INTRINSIC_MATRIX_INT(add_intrinsic2(ctx, fn, dt, dt, dti)); + ADD_INTRINSIC_MATRIX_FLOAT(add_intrinsic2(ctx, fn, dt, dt, dtf)); + + // matrix * vector + add_intrinsic2(ctx, fn, i1, i1x1, i1); + add_intrinsic2(ctx, fn, i1, i1x2, i2); + add_intrinsic2(ctx, fn, i1, i1x3, i3); + add_intrinsic2(ctx, fn, i1, i1x4, i4); + add_intrinsic2(ctx, fn, i2, i2x1, i1); + add_intrinsic2(ctx, fn, i2, i2x2, i2); + add_intrinsic2(ctx, fn, i2, i2x3, i3); + add_intrinsic2(ctx, fn, i2, i2x4, i4); + add_intrinsic2(ctx, fn, i3, i3x1, i1); + add_intrinsic2(ctx, fn, i3, i3x2, i2); + add_intrinsic2(ctx, fn, i3, i3x3, i3); + add_intrinsic2(ctx, fn, i3, i3x4, i4); + add_intrinsic2(ctx, fn, i4, i4x1, i1); + add_intrinsic2(ctx, fn, i4, i4x2, i2); + add_intrinsic2(ctx, fn, i4, i4x3, i3); + add_intrinsic2(ctx, fn, i4, i4x4, i4); + add_intrinsic2(ctx, fn, f1, f1x1, f1); + add_intrinsic2(ctx, fn, f1, f1x2, f2); + add_intrinsic2(ctx, fn, f1, f1x3, f3); + add_intrinsic2(ctx, fn, f1, f1x4, f4); + add_intrinsic2(ctx, fn, f2, f2x1, f1); + add_intrinsic2(ctx, fn, f2, f2x2, f2); + add_intrinsic2(ctx, fn, f2, f2x3, f3); + add_intrinsic2(ctx, fn, f2, f2x4, f4); + add_intrinsic2(ctx, fn, f3, f3x1, f1); + add_intrinsic2(ctx, fn, f3, f3x2, f2); + add_intrinsic2(ctx, fn, f3, f3x3, f3); + add_intrinsic2(ctx, fn, f3, f3x4, f4); + add_intrinsic2(ctx, fn, f4, f4x1, f1); + add_intrinsic2(ctx, fn, f4, f4x2, f2); + add_intrinsic2(ctx, fn, f4, f4x3, f3); + add_intrinsic2(ctx, fn, f4, f4x4, f4); + + // matrix * matrix + add_intrinsic2(ctx, fn, i1x1, i1x1, i1x1); + add_intrinsic2(ctx, fn, i1x2, i1x1, i1x2); + add_intrinsic2(ctx, fn, i1x3, i1x1, i1x3); + add_intrinsic2(ctx, fn, i1x4, i1x1, i1x4); + add_intrinsic2(ctx, fn, i1x1, i1x2, i2x1); + add_intrinsic2(ctx, fn, i1x2, i1x2, i2x2); + add_intrinsic2(ctx, fn, i1x3, i1x2, i2x3); + add_intrinsic2(ctx, fn, i1x4, i1x2, i2x4); + add_intrinsic2(ctx, fn, i1x1, i1x3, i3x1); + add_intrinsic2(ctx, fn, i1x2, i1x3, i3x2); + add_intrinsic2(ctx, fn, i1x3, i1x3, i3x3); + add_intrinsic2(ctx, fn, i1x4, i1x3, i3x4); + add_intrinsic2(ctx, fn, i1x1, i1x4, i4x1); + add_intrinsic2(ctx, fn, i1x2, i1x4, i4x2); + add_intrinsic2(ctx, fn, i1x3, i1x4, i4x3); + add_intrinsic2(ctx, fn, i1x4, i1x4, i4x4); + add_intrinsic2(ctx, fn, i2x1, i2x1, i1x1); + add_intrinsic2(ctx, fn, i2x2, i2x1, i1x2); + add_intrinsic2(ctx, fn, i2x3, i2x1, i1x3); + add_intrinsic2(ctx, fn, i2x4, i2x1, i1x4); + add_intrinsic2(ctx, fn, i2x1, i2x2, i2x1); + add_intrinsic2(ctx, fn, i2x2, i2x2, i2x2); + add_intrinsic2(ctx, fn, i2x3, i2x2, i2x3); + add_intrinsic2(ctx, fn, i2x4, i2x2, i2x4); + add_intrinsic2(ctx, fn, i2x1, i2x3, i3x1); + add_intrinsic2(ctx, fn, i2x2, i2x3, i3x2); + add_intrinsic2(ctx, fn, i2x3, i2x3, i3x3); + add_intrinsic2(ctx, fn, i2x4, i2x3, i3x4); + add_intrinsic2(ctx, fn, i2x1, i2x4, i4x1); + add_intrinsic2(ctx, fn, i2x2, i2x4, i4x2); + add_intrinsic2(ctx, fn, i2x3, i2x4, i4x3); + add_intrinsic2(ctx, fn, i2x4, i2x4, i4x4); + add_intrinsic2(ctx, fn, i3x1, i3x1, i1x1); + add_intrinsic2(ctx, fn, i3x2, i3x1, i1x2); + add_intrinsic2(ctx, fn, i3x3, i3x1, i1x3); + add_intrinsic2(ctx, fn, i3x4, i3x1, i1x4); + add_intrinsic2(ctx, fn, i3x1, i3x2, i2x1); + add_intrinsic2(ctx, fn, i3x2, i3x2, i2x2); + add_intrinsic2(ctx, fn, i3x3, i3x2, i2x3); + add_intrinsic2(ctx, fn, i3x4, i3x2, i2x4); + add_intrinsic2(ctx, fn, i3x1, i3x3, i3x1); + add_intrinsic2(ctx, fn, i3x2, i3x3, i3x2); + add_intrinsic2(ctx, fn, i3x3, i3x3, i3x3); + add_intrinsic2(ctx, fn, i3x4, i3x3, i3x4); + add_intrinsic2(ctx, fn, i3x1, i3x4, i4x1); + add_intrinsic2(ctx, fn, i3x2, i3x4, i4x2); + add_intrinsic2(ctx, fn, i3x3, i3x4, i4x3); + add_intrinsic2(ctx, fn, i3x4, i3x4, i4x4); + add_intrinsic2(ctx, fn, i4x1, i4x1, i1x1); + add_intrinsic2(ctx, fn, i4x2, i4x1, i1x2); + add_intrinsic2(ctx, fn, i4x3, i4x1, i1x3); + add_intrinsic2(ctx, fn, i4x4, i4x1, i1x4); + add_intrinsic2(ctx, fn, i4x1, i4x2, i2x1); + add_intrinsic2(ctx, fn, i4x2, i4x2, i2x2); + add_intrinsic2(ctx, fn, i4x3, i4x2, i2x3); + add_intrinsic2(ctx, fn, i4x4, i4x2, i2x4); + add_intrinsic2(ctx, fn, i4x1, i4x3, i3x1); + add_intrinsic2(ctx, fn, i4x2, i4x3, i3x2); + add_intrinsic2(ctx, fn, i4x3, i4x3, i3x3); + add_intrinsic2(ctx, fn, i4x4, i4x3, i3x4); + add_intrinsic2(ctx, fn, i4x1, i4x4, i4x1); + add_intrinsic2(ctx, fn, i4x2, i4x4, i4x2); + add_intrinsic2(ctx, fn, i4x3, i4x4, i4x3); + add_intrinsic2(ctx, fn, i4x4, i4x4, i4x4); + add_intrinsic2(ctx, fn, f1x1, f1x1, f1x1); + add_intrinsic2(ctx, fn, f1x2, f1x1, f1x2); + add_intrinsic2(ctx, fn, f1x3, f1x1, f1x3); + add_intrinsic2(ctx, fn, f1x4, f1x1, f1x4); + add_intrinsic2(ctx, fn, f1x1, f1x2, f2x1); + add_intrinsic2(ctx, fn, f1x2, f1x2, f2x2); + add_intrinsic2(ctx, fn, f1x3, f1x2, f2x3); + add_intrinsic2(ctx, fn, f1x4, f1x2, f2x4); + add_intrinsic2(ctx, fn, f1x1, f1x3, f3x1); + add_intrinsic2(ctx, fn, f1x2, f1x3, f3x2); + add_intrinsic2(ctx, fn, f1x3, f1x3, f3x3); + add_intrinsic2(ctx, fn, f1x4, f1x3, f3x4); + add_intrinsic2(ctx, fn, f1x1, f1x4, f4x1); + add_intrinsic2(ctx, fn, f1x2, f1x4, f4x2); + add_intrinsic2(ctx, fn, f1x3, f1x4, f4x3); + add_intrinsic2(ctx, fn, f1x4, f1x4, f4x4); + add_intrinsic2(ctx, fn, f2x1, f2x1, f1x1); + add_intrinsic2(ctx, fn, f2x2, f2x1, f1x2); + add_intrinsic2(ctx, fn, f2x3, f2x1, f1x3); + add_intrinsic2(ctx, fn, f2x4, f2x1, f1x4); + add_intrinsic2(ctx, fn, f2x1, f2x2, f2x1); + add_intrinsic2(ctx, fn, f2x2, f2x2, f2x2); + add_intrinsic2(ctx, fn, f2x3, f2x2, f2x3); + add_intrinsic2(ctx, fn, f2x4, f2x2, f2x4); + add_intrinsic2(ctx, fn, f2x1, f2x3, f3x1); + add_intrinsic2(ctx, fn, f2x2, f2x3, f3x2); + add_intrinsic2(ctx, fn, f2x3, f2x3, f3x3); + add_intrinsic2(ctx, fn, f2x4, f2x3, f3x4); + add_intrinsic2(ctx, fn, f2x1, f2x4, f4x1); + add_intrinsic2(ctx, fn, f2x2, f2x4, f4x2); + add_intrinsic2(ctx, fn, f2x3, f2x4, f4x3); + add_intrinsic2(ctx, fn, f2x4, f2x4, f4x4); + add_intrinsic2(ctx, fn, f3x1, f3x1, f1x1); + add_intrinsic2(ctx, fn, f3x2, f3x1, f1x2); + add_intrinsic2(ctx, fn, f3x3, f3x1, f1x3); + add_intrinsic2(ctx, fn, f3x4, f3x1, f1x4); + add_intrinsic2(ctx, fn, f3x1, f3x2, f2x1); + add_intrinsic2(ctx, fn, f3x2, f3x2, f2x2); + add_intrinsic2(ctx, fn, f3x3, f3x2, f2x3); + add_intrinsic2(ctx, fn, f3x4, f3x2, f2x4); + add_intrinsic2(ctx, fn, f3x1, f3x3, f3x1); + add_intrinsic2(ctx, fn, f3x2, f3x3, f3x2); + add_intrinsic2(ctx, fn, f3x3, f3x3, f3x3); + add_intrinsic2(ctx, fn, f3x4, f3x3, f3x4); + add_intrinsic2(ctx, fn, f3x1, f3x4, f4x1); + add_intrinsic2(ctx, fn, f3x2, f3x4, f4x2); + add_intrinsic2(ctx, fn, f3x3, f3x4, f4x3); + add_intrinsic2(ctx, fn, f3x4, f3x4, f4x4); + add_intrinsic2(ctx, fn, f4x1, f4x1, f1x1); + add_intrinsic2(ctx, fn, f4x2, f4x1, f1x2); + add_intrinsic2(ctx, fn, f4x3, f4x1, f1x3); + add_intrinsic2(ctx, fn, f4x4, f4x1, f1x4); + add_intrinsic2(ctx, fn, f4x1, f4x2, f2x1); + add_intrinsic2(ctx, fn, f4x2, f4x2, f2x2); + add_intrinsic2(ctx, fn, f4x3, f4x2, f2x3); + add_intrinsic2(ctx, fn, f4x4, f4x2, f2x4); + add_intrinsic2(ctx, fn, f4x1, f4x3, f3x1); + add_intrinsic2(ctx, fn, f4x2, f4x3, f3x2); + add_intrinsic2(ctx, fn, f4x3, f4x3, f3x3); + add_intrinsic2(ctx, fn, f4x4, f4x3, f3x4); + add_intrinsic2(ctx, fn, f4x1, f4x4, f4x1); + add_intrinsic2(ctx, fn, f4x2, f4x4, f4x2); + add_intrinsic2(ctx, fn, f4x3, f4x4, f4x3); + add_intrinsic2(ctx, fn, f4x4, f4x4, f4x4); +} // add_intrinsic_mul + +static void init_builtins(Context *ctx) +{ + // 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 + + // !!! FIXME: block these out by pixel/vertex/etc shader. + // !!! FIXME: calculate actual shader model (or maybe just let bytecode verifier throw up?). + const int shader_model = 3; + if (shader_model >= 1) + { + add_intrinsic_SAME1_ANYfi(ctx, stringcache(ctx->strcache, "abs")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "acos")); + add_intrinsic_BOOL_ANYfib(ctx, stringcache(ctx->strcache, "all")); + add_intrinsic_BOOL_ANYfib(ctx, stringcache(ctx->strcache, "any")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "asin")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "atan")); + add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "atan2")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "ceil")); + add_intrinsic_SAME1_ANYfi_SAME1_SAME1(ctx, stringcache(ctx->strcache, "clamp")); + add_intrinsic_VOID_ANYf(ctx, stringcache(ctx->strcache, "clip")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "cos")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "cosh")); + add_intrinsic_3f_3f_3f(ctx, stringcache(ctx->strcache, "cross")); + add_intrinsic_4i_4f(ctx, stringcache(ctx->strcache, "D3DCOLORtoUBYTE4")); + add_intrinsic_f_Vf_SAME1(ctx, stringcache(ctx->strcache, "distance")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "degrees")); + add_intrinsic_f_SQUAREMATRIXf(ctx, stringcache(ctx->strcache, "determinant")); + add_intrinsic_fi_Vfi_SAME1(ctx, stringcache(ctx->strcache, "dot")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "exp")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "exp2")); + add_intrinsic_SAME1_Vf_SAME1_SAME1(ctx, stringcache(ctx->strcache, "faceforward")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "floor")); + add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "fmod")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "frac")); + add_intrinsic_BOOL_ANYf(ctx, stringcache(ctx->strcache, "isfinite")); + add_intrinsic_BOOL_ANYf(ctx, stringcache(ctx->strcache, "isinf")); + add_intrinsic_BOOL_ANYf(ctx, stringcache(ctx->strcache, "isnan")); + add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "ldexp")); + add_intrinsic_f_Vf(ctx, stringcache(ctx->strcache, "length")); + add_intrinsic_SAME1_ANYf_SAME1_SAME1(ctx, stringcache(ctx->strcache, "lerp")); + add_intrinsic_4f_f_f_f(ctx, stringcache(ctx->strcache, "lit")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "log")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "log10")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "log2")); + add_intrinsic_SAME1_ANYfi_SAME1(ctx, stringcache(ctx->strcache, "max")); + add_intrinsic_SAME1_ANYfi_SAME1(ctx, stringcache(ctx->strcache, "min")); + add_intrinsic_SAME1_ANYfi_SAME1(ctx, stringcache(ctx->strcache, "modf")); // !!! FIXME: out var? + add_intrinsic_mul(ctx, stringcache(ctx->strcache, "mul")); + add_intrinsic_f_Vf(ctx, stringcache(ctx->strcache, "noise")); + add_intrinsic_SAME1_Vf(ctx, stringcache(ctx->strcache, "normalize")); + add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "pow")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "radians")); + add_intrinsic_SAME1_ANYfi_SAME1(ctx, stringcache(ctx->strcache, "reflect")); + add_intrinsic_SAME1_Vf_SAME1_f(ctx, stringcache(ctx->strcache, "refract")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "round")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "rsqrt")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "saturate")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "sign")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "sin")); + add_intrinsic_VOID_ANYf_SAME1_SAME1(ctx, stringcache(ctx->strcache, "sincos")); // !!! FIXME: out var? + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "sinh")); + add_intrinsic_SAME1_ANYf_SAME1_SAME1(ctx, stringcache(ctx->strcache, "smoothstep")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "sqrt")); + add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "step")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "tan")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "tanh")); + add_intrinsic_4f_s1_f(ctx, stringcache(ctx->strcache, "tex1D")); + add_intrinsic_4f_s2_2f(ctx, stringcache(ctx->strcache, "tex2D")); + add_intrinsic_4f_s3_3f(ctx, stringcache(ctx->strcache, "tex3D")); + add_intrinsic_4f_sc_3f(ctx, stringcache(ctx->strcache, "texCUBE")); + add_intrinsic_SAME1_Mfib(ctx, stringcache(ctx->strcache, "transpose")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "trunc")); + } // if + + if (shader_model >= 2) + { + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "ddx")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "ddy")); + add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "frexp")); + add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "fwidth")); + add_intrinsic_4f_s1_f_f_f(ctx, stringcache(ctx->strcache, "tex1D")); + add_intrinsic_4f_s1_4f(ctx, stringcache(ctx->strcache, "tex1Dbias")); + add_intrinsic_4f_s1_f_f_f(ctx, stringcache(ctx->strcache, "tex1Dgrad")); + add_intrinsic_4f_s1_4f(ctx, stringcache(ctx->strcache, "tex1Dproj")); + add_intrinsic_4f_s2_2f_2f_2f(ctx, stringcache(ctx->strcache, "tex2D")); + add_intrinsic_4f_s2_4f(ctx, stringcache(ctx->strcache, "tex2Dbias")); + add_intrinsic_4f_s2_2f_2f_2f(ctx, stringcache(ctx->strcache, "tex2Dgrad")); + add_intrinsic_4f_s2_4f(ctx, stringcache(ctx->strcache, "tex2Dproj")); + add_intrinsic_4f_s3_3f_3f_3f(ctx, stringcache(ctx->strcache, "tex3D")); + add_intrinsic_4f_s3_4f(ctx, stringcache(ctx->strcache, "tex3Dbias")); + add_intrinsic_4f_s3_3f_3f_3f(ctx, stringcache(ctx->strcache, "tex3Dgrad")); + add_intrinsic_4f_s3_4f(ctx, stringcache(ctx->strcache, "tex3Dproj")); + add_intrinsic_4f_sc_3f_3f_3f(ctx, stringcache(ctx->strcache, "texCUBE")); + add_intrinsic_4f_sc_4f(ctx, stringcache(ctx->strcache, "texCUBEbias")); + add_intrinsic_4f_sc_3f_3f_3f(ctx, stringcache(ctx->strcache, "texCUBEgrad")); + add_intrinsic_4f_sc_4f(ctx, stringcache(ctx->strcache, "texCUBEproj")); + } // if + + if (shader_model >= 3) + { + add_intrinsic_4f_s1_4f(ctx, stringcache(ctx->strcache, "tex1Dlod")); + add_intrinsic_4f_s2_4f(ctx, stringcache(ctx->strcache, "tex2Dlod")); + add_intrinsic_4f_s3_4f(ctx, stringcache(ctx->strcache, "tex3Dlod")); + add_intrinsic_4f_sc_4f(ctx, stringcache(ctx->strcache, "texCUBElod")); + } // if +} // init_builtins + + +// 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)... + + init_builtins(ctx); + + SymbolScope *start_scope = ctx->usertypes.scope; + + #if DEBUG_COMPILER_PARSER + ParseHLSLTrace(stdout, "COMPILER: "); + #endif + + // 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); // !!! FIXME: do we need this? It's kind of useless during parsing. + 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); + + // Clean out extra usertypes; they are dummies until semantic analysis. + while (ctx->usertypes.scope != start_scope) + pop_symbol(ctx, &ctx->usertypes); + + ParseHLSLFree(parser, ctx->free, ctx->malloc_data); + preprocessor_end(pp); +} // parse_source + + +/* Intermediate representation... */ + +static inline int generate_ir_label(Context *ctx) +{ + return ctx->ir_label_count++; +} // generate_ir_label + +static inline int generate_ir_temp(Context *ctx) +{ + return ctx->ir_temp_count++; +} // generate_ir_temp + +static const LoopLabels *push_ir_loop(Context *ctx, const int isswitch) +{ + // !!! FIXME: cache these allocations? + LoopLabels *retval = Malloc(ctx, sizeof (LoopLabels)); + if (retval) + { + retval->start = (isswitch) ? -1 : generate_ir_label(ctx); + retval->end = generate_ir_label(ctx); + retval->prev = ctx->ir_loop; + ctx->ir_loop = retval; + } // if + + return retval; +} // push_ir_loop + +static void pop_ir_loop(Context *ctx) +{ + assert(ctx->ir_loop != NULL); + LoopLabels *labels = ctx->ir_loop; + ctx->ir_loop = ctx->ir_loop->prev; + Free(ctx, labels); +} // pop_ir_loop + + +#define NEW_IR_NODE(retval, cls, typ) \ + cls *retval = (cls *) Malloc(ctx, sizeof (cls)); \ + do { \ + if (retval == NULL) { return NULL; } \ + retval->ir.type = typ; \ + retval->ir.filename = ctx->sourcefile; \ + retval->ir.line = ctx->sourceline; \ + } while (0) + +#define NEW_IR_EXPR(retval, cls, typ, dt, numelems) \ + cls *retval = (cls *) Malloc(ctx, sizeof (cls)); \ + do { \ + if (retval == NULL) { return NULL; } \ + retval->info.ir.type = typ; \ + retval->info.ir.filename = ctx->sourcefile; \ + retval->info.ir.line = ctx->sourceline; \ + retval->info.type = dt; \ + retval->info.elements = numelems; \ + } while (0) + +// syntactic sugar. +static inline MOJOSHADER_irNode *build_ir(Context *ctx, void *_ast); +static inline MOJOSHADER_irExpression *build_ir_expr(Context *ctx, void *_ast) +{ + MOJOSHADER_irNode *retval = build_ir(ctx, _ast); + assert(!retval || (retval->ir.type > MOJOSHADER_IR_START_RANGE_EXPR)); + assert(!retval || (retval->ir.type < MOJOSHADER_IR_END_RANGE_EXPR)); + return (MOJOSHADER_irExpression *) retval; +} // build_ir_expr + +static inline MOJOSHADER_irStatement *build_ir_stmt(Context *ctx, void *_ast) +{ + MOJOSHADER_irNode *retval = build_ir(ctx, _ast); + assert(!retval || (retval->ir.type > MOJOSHADER_IR_START_RANGE_STMT)); + assert(!retval || (retval->ir.type < MOJOSHADER_IR_END_RANGE_STMT)); + return (MOJOSHADER_irStatement *) retval; +} // build_ir_stmt + + +static MOJOSHADER_irExpression *new_ir_binop(Context *ctx, + const MOJOSHADER_irBinOpType op, + MOJOSHADER_irExpression *left, + MOJOSHADER_irExpression *right) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irBinOp, MOJOSHADER_IR_BINOP, left->info.type, left->info.elements); + assert(left->info.type == right->info.type); + assert(left->info.elements == right->info.elements); + retval->op = op; + retval->left = left; + retval->right = right; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_binop + +static MOJOSHADER_irExpression *new_ir_eseq(Context *ctx, + MOJOSHADER_irStatement *stmt, + MOJOSHADER_irExpression *expr) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irESeq, MOJOSHADER_IR_ESEQ, expr->info.type, expr->info.elements); + retval->stmt = stmt; + retval->expr = expr; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_eseq + +static MOJOSHADER_irExpression *new_ir_temp(Context *ctx, const int index, + const MOJOSHADER_astDataTypeType type, + const int elements) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irTemp, MOJOSHADER_IR_TEMP, type, elements); + retval->index = index; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_temp + + + +#define NEW_IR_BINOP(op,l,r) new_ir_binop(ctx, MOJOSHADER_IR_BINOP_##op, l, r) +#define EASY_IR_BINOP(op) \ + NEW_IR_BINOP(op, build_ir_expr(ctx, ast->binary.left), \ + build_ir_expr(ctx, ast->binary.right)) + +// You have to fill in ->value yourself! +static MOJOSHADER_irExpression *new_ir_constant(Context *ctx, + const MOJOSHADER_astDataTypeType type, + const int elements) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irConstant, MOJOSHADER_IR_CONSTANT, type, elements); + return (MOJOSHADER_irExpression *) retval; +} // new_ir_constant + +static MOJOSHADER_irExpression *new_ir_constint(Context *ctx, const int val) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irConstant, MOJOSHADER_IR_CONSTANT, MOJOSHADER_AST_DATATYPE_INT, 1); + retval->value.ival[0] = val; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_constint + +static MOJOSHADER_irExpression *new_ir_constfloat(Context *ctx, const float val) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irConstant, MOJOSHADER_IR_CONSTANT, MOJOSHADER_AST_DATATYPE_FLOAT, 1); + retval->value.fval[0] = val; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_constfloat + +static MOJOSHADER_irExpression *new_ir_constbool(Context *ctx, const int val) +{ + // !!! FIXME: cache true and false in (ctx), ignore in delete_ir(). + NEW_IR_EXPR(retval, MOJOSHADER_irConstant, MOJOSHADER_IR_CONSTANT, MOJOSHADER_AST_DATATYPE_BOOL, 1); + retval->value.ival[0] = val; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_constbool + +static MOJOSHADER_irExpression *new_ir_convert(Context *ctx, MOJOSHADER_irExpression *expr, + const MOJOSHADER_astDataTypeType type, + const int elements) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irConvert, MOJOSHADER_IR_CONVERT, type, elements); + retval->expr = expr; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_convert + +static MOJOSHADER_irExpression *new_ir_construct(Context *ctx, MOJOSHADER_irExprList *args, + const MOJOSHADER_astDataTypeType type, + const int elements) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irConstruct, MOJOSHADER_IR_CONSTRUCT, type, elements); + retval->args = args; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_construct + +static MOJOSHADER_irExpression *new_ir_call(Context *ctx, const int index, + MOJOSHADER_irExprList *args, + const MOJOSHADER_astDataTypeType type, + const int elements) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irCall, MOJOSHADER_IR_CALL, type, elements); + retval->args = args; + retval->index = index; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_call + +static MOJOSHADER_irExpression *new_ir_swizzle(Context *ctx, + MOJOSHADER_irExpression *expr, + const char *channels, + const MOJOSHADER_astDataTypeType type, + const int elements) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irSwizzle, MOJOSHADER_IR_SWIZZLE, type, elements); + retval->expr = expr; + memcpy(retval->channels, channels, sizeof (retval->channels)); + return (MOJOSHADER_irExpression *) retval; +} // new_ir_swizzle + +static MOJOSHADER_irExpression *new_ir_memory(Context *ctx, const int index, + const MOJOSHADER_astDataTypeType type, + const int elements) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irMemory, MOJOSHADER_IR_MEMORY, type, elements); + retval->index = index; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_memory + +static MOJOSHADER_irExpression *new_ir_array(Context *ctx, + MOJOSHADER_irExpression *array, + MOJOSHADER_irExpression *element, + const MOJOSHADER_astDataTypeType type, + const int elements) +{ + NEW_IR_EXPR(retval, MOJOSHADER_irArray, MOJOSHADER_IR_ARRAY, type, elements); + retval->array = array; + retval->element = element; + return (MOJOSHADER_irExpression *) retval; +} // new_ir_array + +static MOJOSHADER_irStatement *new_ir_seq(Context *ctx, + MOJOSHADER_irStatement *first, + MOJOSHADER_irStatement *next) +{ + assert((first != NULL) || (next != NULL)); + if (first == NULL) // don't generate a SEQ if unnecessary. + return next; + else if (next == NULL) + return first; + + NEW_IR_NODE(retval, MOJOSHADER_irSeq, MOJOSHADER_IR_SEQ); + retval->first = first; + retval->next = next; + return (MOJOSHADER_irStatement *) retval; +} // new_ir_seq + +static MOJOSHADER_irStatement *new_ir_jump(Context *ctx, const int label) +{ + NEW_IR_NODE(retval, MOJOSHADER_irJump, MOJOSHADER_IR_JUMP); + retval->label = label; + return (MOJOSHADER_irStatement *) retval; +} // new_ir_jump + +static MOJOSHADER_irStatement *new_ir_cjump(Context *ctx, + const MOJOSHADER_irConditionType cond, + MOJOSHADER_irExpression *left, + MOJOSHADER_irExpression *right, + const int iftrue, const int iffalse) +{ + NEW_IR_NODE(retval, MOJOSHADER_irCJump, MOJOSHADER_IR_CJUMP); + retval->cond = cond; + retval->left = left; + retval->right = right; + retval->iftrue = iftrue; + retval->iffalse = iffalse; + return (MOJOSHADER_irStatement *) retval; +} // new_ir_cjump + +static MOJOSHADER_irStatement *new_ir_label(Context *ctx, const int index) +{ + NEW_IR_NODE(retval, MOJOSHADER_irLabel, MOJOSHADER_IR_LABEL); + retval->index = index; + return (MOJOSHADER_irStatement *) retval; +} // new_ir_label + +static MOJOSHADER_irStatement *new_ir_move(Context *ctx, + MOJOSHADER_irExpression *dst, + MOJOSHADER_irExpression *src, + const int writemask) +{ + NEW_IR_NODE(retval, MOJOSHADER_irMove, MOJOSHADER_IR_MOVE); + assert(dst->info.type == src->info.type); + assert(dst->info.elements == src->info.elements); + retval->dst = dst; + retval->src = src; + retval->writemask = writemask; + return (MOJOSHADER_irStatement *) retval; +} // new_ir_move + +static MOJOSHADER_irStatement *new_ir_expr_stmt(Context *ctx, MOJOSHADER_irExpression *expr) +{ + NEW_IR_NODE(retval, MOJOSHADER_irExprStmt, MOJOSHADER_IR_EXPR_STMT); + retval->expr = expr; + return (MOJOSHADER_irStatement *) retval; +} // new_ir_expr_stmt + +static MOJOSHADER_irStatement *new_ir_discard(Context *ctx) +{ + NEW_IR_NODE(retval, MOJOSHADER_irDiscard, MOJOSHADER_IR_DISCARD); + return (MOJOSHADER_irStatement *) retval; +} // new_ir_discard + +static MOJOSHADER_irExprList *new_ir_exprlist(Context *ctx, MOJOSHADER_irExpression *expr) +{ + NEW_IR_NODE(retval, MOJOSHADER_irExprList, MOJOSHADER_IR_EXPRLIST); + retval->expr = expr; + retval->next = NULL; + return (MOJOSHADER_irExprList *) retval; +} // new_ir_exprlist + + +// This handles most comparison operators (less-than, equals, etc...) +static MOJOSHADER_irExpression *build_ir_compare(Context *ctx, + const MOJOSHADER_irConditionType operation, + MOJOSHADER_irExpression *left, + MOJOSHADER_irExpression *right, + MOJOSHADER_irExpression *tval, + MOJOSHADER_irExpression *fval) +{ + /* The gist... + cjump x < y, t, f // '<' is whatever operation + t: + move tmp, tval + jump join + f: + move tmp, fval + join: + */ + + const int t = generate_ir_label(ctx); + const int f = generate_ir_label(ctx); + const int join = generate_ir_label(ctx); + const int tmp = generate_ir_temp(ctx); + + assert(tval->info.type == fval->info.type); + assert(tval->info.elements == fval->info.elements); + + const MOJOSHADER_astDataTypeType dt = tval->info.type; + const int elements = tval->info.elements; + + return new_ir_eseq(ctx, + new_ir_seq(ctx, new_ir_cjump(ctx, operation, left, right, t, f), + new_ir_seq(ctx, new_ir_label(ctx, t), + new_ir_seq(ctx, new_ir_move(ctx, new_ir_temp(ctx, tmp, dt, elements), tval, -1), + new_ir_seq(ctx, new_ir_jump(ctx, join), + new_ir_seq(ctx, new_ir_label(ctx, f), + new_ir_seq(ctx, new_ir_move(ctx, new_ir_temp(ctx, tmp, dt, elements), fval, -1), + new_ir_label(ctx, join))))))), + new_ir_temp(ctx, tmp, dt, elements)); +} // build_ir_compare + +#define EASY_IR_COMPARE(op) \ + build_ir_compare(ctx, MOJOSHADER_IR_COND_##op, \ + build_ir_expr(ctx, ast->binary.left), \ + build_ir_expr(ctx, ast->binary.right), \ + new_ir_constbool(ctx, 1), \ + new_ir_constbool(ctx, 0)) + + +// This handles && and || operators. +static MOJOSHADER_irExpression *build_ir_logical_and_or(Context *ctx, + const MOJOSHADER_astExpressionBinary *ast, + const int left_testval) +{ + /* The gist... + cjump left == left_testval, maybe, f + maybe: + cjump right == true, t, f + t: + move tmp, 1 + jump join + f: + move tmp, 0 + join: + */ + + assert(ast->left->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL); + assert(ast->right->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL); + + const int t = generate_ir_label(ctx); + const int f = generate_ir_label(ctx); + const int maybe = generate_ir_label(ctx); + const int join = generate_ir_label(ctx); + const int tmp = generate_ir_temp(ctx); + + return new_ir_eseq(ctx, + new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->left), new_ir_constbool(ctx, left_testval), maybe, f), + new_ir_seq(ctx, new_ir_label(ctx, maybe), + new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->right), new_ir_constbool(ctx, 1), t, f), + new_ir_seq(ctx, new_ir_label(ctx, t), + new_ir_seq(ctx, new_ir_move(ctx, new_ir_temp(ctx, tmp, MOJOSHADER_AST_DATATYPE_BOOL, 1), new_ir_constbool(ctx, 1), -1), + new_ir_seq(ctx, new_ir_jump(ctx, join), + new_ir_seq(ctx, new_ir_label(ctx, f), + new_ir_seq(ctx, new_ir_move(ctx, new_ir_temp(ctx, tmp, MOJOSHADER_AST_DATATYPE_BOOL, 1), new_ir_constbool(ctx, 0), -1), + new_ir_label(ctx, join))))))))), + new_ir_temp(ctx, tmp, MOJOSHADER_AST_DATATYPE_BOOL, 1)); +} // build_ir_logical_and_or + +static inline MOJOSHADER_irExpression *build_ir_logical_and(Context *ctx, + const MOJOSHADER_astExpressionBinary *ast) +{ + // this needs to not evaluate (right) if (left) is false! + return build_ir_logical_and_or(ctx, ast, 1); +} // build_ir_logical_and + +static inline MOJOSHADER_irExpression *build_ir_logical_or(Context *ctx, + const MOJOSHADER_astExpressionBinary *ast) +{ + // this needs to not evaluate (right) if (left) is true! + return build_ir_logical_and_or(ctx, ast, 0); +} // build_ir_logical_or + +static inline MOJOSHADER_irStatement *build_ir_no_op(Context *ctx) +{ + return new_ir_label(ctx, generate_ir_label(ctx)); +} // build_ir_no_op + +static MOJOSHADER_irStatement *build_ir_ifstmt(Context *ctx, + const MOJOSHADER_astIfStatement *ast) +{ + assert(ast->expr->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL); + + // !!! FIXME: ast->attributes? + + // IF statement without an ELSE. + if (ast->else_statement == NULL) + { + /* The gist... + cjump expr, t, join + t: + statement + join: + */ + + const int t = generate_ir_label(ctx); + const int join = generate_ir_label(ctx); + + return new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->expr), new_ir_constbool(ctx, 1), t, join), + new_ir_seq(ctx, new_ir_label(ctx, t), + new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement), + new_ir_seq(ctx, new_ir_label(ctx, join), + build_ir_stmt(ctx, ast->next))))); + } // if + + // IF statement _with_ an ELSE. + /* The gist... + cjump expr, t, f + t: + statement + jump join + f: + elsestatement + join: + */ + + const int t = generate_ir_label(ctx); + const int f = generate_ir_label(ctx); + const int join = generate_ir_label(ctx); + + return new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->expr), new_ir_constbool(ctx, 1), t, f), + new_ir_seq(ctx, new_ir_label(ctx, t), + new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement), + new_ir_seq(ctx, new_ir_jump(ctx, join), + new_ir_seq(ctx, new_ir_label(ctx, f), + new_ir_seq(ctx, build_ir_stmt(ctx, ast->else_statement), + new_ir_seq(ctx, new_ir_label(ctx, join), + build_ir_stmt(ctx, ast->next)))))))); +} // build_ir_ifstmt + + +static MOJOSHADER_irStatement *build_ir_forstmt(Context *ctx, + const MOJOSHADER_astForStatement *ast) +{ + // !!! FIXME: ast->unroll + + assert(ast->looptest->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL); + + /* The gist... + initializer // (or var_decl->initializer!) + test: + cjump looptest == true, loop, join + loop: + statement + increment: // needs to be here; this is where "continue" jumps! + counter + jump test + join: + */ + + const int test = generate_ir_label(ctx); + const int loop = generate_ir_label(ctx); + + const LoopLabels *labels = push_ir_loop(ctx, 0); + if (labels == NULL) + return NULL; // out of memory... + + const int increment = labels->start; + const int join = labels->end; + + assert( (ast->var_decl && !ast->initializer) || + (!ast->var_decl && ast->initializer) ); + + MOJOSHADER_irStatement *init = NULL; + if (ast->var_decl != NULL) + { +//sdfsdf + // !!! FIXME: map the initializer to the variable? Need fix to var_decl parsing. +// new_ir_move(ctx, FIXME MAP TO REGISTER ast->var_decl->index, build_ir_expr(ctx, ast->fsdf)); +// FIXME +// init = build_ir_vardecl(ctx, ast->var_decl); + } // if + else + { +// init = build_ir_expr(ctx, ast->initializer); + } // else + + MOJOSHADER_irStatement *retval = + new_ir_seq(ctx, init, + new_ir_seq(ctx, new_ir_label(ctx, test), + new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->looptest), new_ir_constbool(ctx, 1), loop, join), + new_ir_seq(ctx, new_ir_label(ctx, loop), + new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement), + new_ir_seq(ctx, new_ir_label(ctx, increment), + new_ir_seq(ctx, new_ir_expr_stmt(ctx, build_ir_expr(ctx, ast->counter)), + new_ir_seq(ctx, new_ir_jump(ctx, test), + new_ir_label(ctx, join))))))))); + + pop_ir_loop(ctx); + + return new_ir_seq(ctx, retval, build_ir_stmt(ctx, ast->next)); +} // build_ir_forstmt + +static MOJOSHADER_irStatement *build_ir_whilestmt(Context *ctx, + const MOJOSHADER_astWhileStatement *ast) +{ + // !!! FIXME: ast->unroll + + assert(ast->expr->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL); + + /* The gist... + loop: + cjump expr == true, t, join + t: + statement + jump loop + join: + */ + + const LoopLabels *labels = push_ir_loop(ctx, 0); + if (labels == NULL) + return NULL; // out of memory... + + const int loop = labels->start; + const int t = generate_ir_label(ctx); + const int join = labels->end; + + MOJOSHADER_irStatement *retval = + new_ir_seq(ctx, new_ir_label(ctx, loop), + new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->expr), new_ir_constbool(ctx, 1), t, join), + new_ir_seq(ctx, new_ir_label(ctx, t), + new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement), + new_ir_seq(ctx, new_ir_jump(ctx, loop), + new_ir_label(ctx, join)))))); + + pop_ir_loop(ctx); + + return new_ir_seq(ctx, retval, build_ir_stmt(ctx, ast->next)); +} // build_ir_whilestmt + +static MOJOSHADER_irStatement *build_ir_dostmt(Context *ctx, + const MOJOSHADER_astDoStatement *ast) +{ + // !!! FIXME: ast->unroll + + assert(ast->expr->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL); + + /* The gist... + loop: + statement + cjump expr == true, loop, join + join: + */ + + const LoopLabels *labels = push_ir_loop(ctx, 0); + if (labels == NULL) + return NULL; // out of memory... + + const int loop = labels->start; + const int join = labels->end; + + MOJOSHADER_irStatement *retval = + new_ir_seq(ctx, new_ir_label(ctx, loop), + new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement), + new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->expr), new_ir_constbool(ctx, 1), loop, join), + new_ir_label(ctx, join)))); + + pop_ir_loop(ctx); + + return new_ir_seq(ctx, retval, build_ir_stmt(ctx, ast->next)); +} // build_ir_dostmt + +static MOJOSHADER_irStatement *build_ir_switch(Context *ctx, const MOJOSHADER_astSwitchStatement *ast) +{ + // Dithering down to a list of if-statements in all cases + // isn't ideal, but we can't do jumptables in D3D bytecode. + + // !!! FIXME: attributes? + + /* The gist... + move tmp, expr + cjump tmp == case1expr, case1, testcase2 + testcase2: // etc + cjump tmp == case2expr, case2, join + case1: + case1stmt // might have a break in it somewhere. + case2: + case2stmt + join: + */ + + const LoopLabels *labels = push_ir_loop(ctx, 1); + if (labels == NULL) + return NULL; // out of memory... + + const int join = labels->end; + const int elems = datatype_elems(ctx, ast->expr->datatype); + const MOJOSHADER_astDataTypeType dt = datatype_base(ctx, ast->expr->datatype)->type; + + const MOJOSHADER_astSwitchCases *cases = ast->cases; + const int tmp = generate_ir_temp(ctx); + MOJOSHADER_irStatement *startseqs = new_ir_move(ctx, new_ir_temp(ctx, tmp, dt, elems), build_ir_expr(ctx, ast->expr), -1); + MOJOSHADER_irStatement *testseqs = startseqs; + MOJOSHADER_irStatement *startcaseseqs = NULL; + MOJOSHADER_irStatement *caseseqs = NULL; + while (cases) + { + const int t = generate_ir_label(ctx); + const int f = (cases->next == NULL) ? join : generate_ir_label(ctx); + MOJOSHADER_irStatement *cjump = new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, cases->expr), new_ir_temp(ctx, tmp, dt, elems), t, f); + + if (cases->next == NULL) // last one, do the join label. + { + testseqs = new_ir_seq(ctx, testseqs, cjump); + caseseqs = new_ir_seq(ctx, caseseqs, new_ir_seq(ctx, new_ir_label(ctx, t), build_ir_stmt(ctx, cases->statement))); + caseseqs = new_ir_seq(ctx, caseseqs, new_ir_label(ctx, f)); + } // if + else + { + testseqs = new_ir_seq(ctx, testseqs, new_ir_seq(ctx, cjump, new_ir_label(ctx, f))); + caseseqs = new_ir_seq(ctx, caseseqs, new_ir_seq(ctx, new_ir_label(ctx, t), build_ir_stmt(ctx, cases->statement))); + } // else + + if (startcaseseqs == NULL) + startcaseseqs = caseseqs; + + cases = cases->next; + } // while + + pop_ir_loop(ctx); + + return new_ir_seq(ctx, startseqs, new_ir_seq(ctx, startcaseseqs, build_ir_stmt(ctx, ast->next))); +} // build_ir_switch + +static MOJOSHADER_irExpression *build_ir_increxpr(Context *ctx, const MOJOSHADER_astDataType *_dt, + const int val) +{ + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, _dt); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + MOJOSHADER_irConstant *retval = (MOJOSHADER_irConstant *) new_ir_constant(ctx, type, elems); + int i; + + switch (type) + { + case MOJOSHADER_AST_DATATYPE_BOOL: + case MOJOSHADER_AST_DATATYPE_INT: + case MOJOSHADER_AST_DATATYPE_UINT: + for (i = 0; i < elems; i++) + retval->value.ival[i] = (int) val; + break; + + 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: + for (i = 0; i < elems; i++) + retval->value.fval[i] = (float) val; + break; + + default: + assert(0 && "Semantic analysis should have caught this!"); + } // switch + + return (MOJOSHADER_irExpression *) retval; +} // build_ir_increxpr + +static MOJOSHADER_irExpression *build_ir_preincdec(Context *ctx, MOJOSHADER_astExpressionUnary *ast, const MOJOSHADER_irBinOpType binop) +{ + /* The gist... + move expr, expr + 1 + return expr + */ + // !!! FIXME: can you writemask an increment operator? + MOJOSHADER_irExpression *constant = build_ir_increxpr(ctx, ast->datatype, 1); + return new_ir_eseq(ctx, + new_ir_move(ctx, + build_ir_expr(ctx, ast->operand), + new_ir_binop(ctx, binop, build_ir_expr(ctx, ast->operand), constant), -1), + build_ir_expr(ctx, ast->operand)); +} // build_ir_preincdec + +static MOJOSHADER_irExpression *build_ir_postincdec(Context *ctx, MOJOSHADER_astExpressionUnary *ast, const MOJOSHADER_irBinOpType binop) +{ + /* The gist... + move tmp, expr + move expr, expr + 1 + return tmp + */ + + // !!! FIXME: can you writemask an increment operator? + MOJOSHADER_irExpression *constant = build_ir_increxpr(ctx, ast->datatype, 1); + const int tmp = generate_ir_temp(ctx); + return new_ir_eseq(ctx, + new_ir_seq(ctx, + new_ir_move(ctx, new_ir_temp(ctx, tmp, constant->info.type, constant->info.elements), build_ir_expr(ctx, ast->operand), -1), + new_ir_move(ctx, build_ir_expr(ctx, ast->operand), + new_ir_binop(ctx, binop, build_ir_expr(ctx, ast->operand), constant), -1)), + new_ir_temp(ctx, tmp, constant->info.type, constant->info.elements)); +} // build_ir_postincdec + +static MOJOSHADER_irExpression *build_ir_convert(Context *ctx, const MOJOSHADER_astExpressionCast *ast) +{ + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + return new_ir_convert(ctx, build_ir_expr(ctx, ast->operand), type, elems); +} // build_ir_convert + +static MOJOSHADER_irExprList *build_ir_exprlist(Context *ctx, MOJOSHADER_astArguments *args) +{ + MOJOSHADER_irExprList *retval = NULL; + MOJOSHADER_irExprList *prev = NULL; + + while (args != NULL) + { + assert((retval && prev) || ((!retval) && (!prev))); + + MOJOSHADER_irExprList *item = new_ir_exprlist(ctx, build_ir_expr(ctx, args->argument)); + if (prev == NULL) + prev = retval = item; + else + prev->next = item; + + args = args->next; + } // while + + return retval; +} // build_ir_exprlist + +static MOJOSHADER_irExpression *build_ir_constructor(Context *ctx, const MOJOSHADER_astExpressionConstructor *ast) +{ + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + assert(elems <= 16); // just in case (matrix4x4 constructor is largest). + return new_ir_construct(ctx, build_ir_exprlist(ctx, ast->args), type, elems); +} // build_ir_constructor + +static MOJOSHADER_irExpression *build_ir_call(Context *ctx, const MOJOSHADER_astExpressionCallFunction *ast) +{ + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + return new_ir_call(ctx, ast->identifier->index, build_ir_exprlist(ctx, ast->args), type, elems); +} // build_ir_call + +static char swiz_to_channel(const char swiz) +{ + if ((swiz == 'r') || (swiz == 'x')) return 0; + if ((swiz == 'g') || (swiz == 'y')) return 1; + if ((swiz == 'b') || (swiz == 'z')) return 2; + if ((swiz == 'a') || (swiz == 'w')) return 3; + assert(0 && "Should have been caught by semantic analysis."); + return 0; +} // swiz_to_channel + +static MOJOSHADER_irExpression *build_ir_swizzle(Context *ctx, const MOJOSHADER_astExpressionDerefStruct *ast) +{ + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + char chans[4] = { 0, 0, 0, 0 }; + const char *swizstr = ast->member; + int i; + + for (i = 0; swizstr[i]; i++) + chans[i] = swiz_to_channel(swizstr[i]); + + return new_ir_swizzle(ctx, build_ir_expr(ctx, ast->identifier), chans, type, elems); +} // build_ir_swizzle + +static MOJOSHADER_irExpression *build_ir_identifier(Context *ctx, const MOJOSHADER_astExpressionIdentifier *ast) +{ + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + return new_ir_memory(ctx, ast->index, type, elems); +} // build_ir_identifier + +static MOJOSHADER_irExpression *build_ir_derefstruct(Context *ctx, const MOJOSHADER_astExpressionDerefStruct *ast) +{ + // There are only three possible IR nodes that contain a struct: + // an irTemp, an irMemory, or an irESeq that results in a temp or memory. + // As such, we figure out which it is, and offset appropriately for the + // member. + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + MOJOSHADER_irExpression *expr = build_ir_expr(ctx, ast->identifier); + MOJOSHADER_irExpression *finalexpr = expr; + + assert(!ast->isswizzle); + + while (finalexpr->ir.type == MOJOSHADER_IR_ESEQ) + finalexpr = finalexpr->eseq.expr; + + if (finalexpr->ir.type == MOJOSHADER_IR_TEMP) + finalexpr->temp.index += ast->member_index; + else if (finalexpr->ir.type == MOJOSHADER_IR_MEMORY) + finalexpr->memory.index += ast->member_index; + else + assert(0 && "Unexpected condition"); + + // Replace the struct type with the type of the member. + expr->info.type = type; + expr->info.elements = elems; + + return expr; +} // build_ir_derefstruct + +static MOJOSHADER_irExpression *build_ir_derefarray(Context *ctx, const MOJOSHADER_astExpressionBinary *ast) +{ + // In most compilers, arrays dither down to offsets into memory, but + // they're somewhat special in D3D, since they might have to deal with + // vectors, etc...so we keep them as first-class citizens of the IR, + // and let the optimizer/codegen sort it out. + // !!! FIXME: this might be the wrong move. Maybe remove this IR node type? + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + + // !!! FIXME: Array dereference of a vector can become a simple swizzle operation, if we have a constant index. + // !!! FIXME: Matrix dereference of a vector can become a simple reference to a temp/memory, if we have a constant index. + return new_ir_array(ctx, build_ir_expr(ctx, ast->left), build_ir_expr(ctx, ast->right), type, elems); +} // build_ir_derefarray + +static MOJOSHADER_irExpression *build_ir_assign_binop(Context *ctx, + const MOJOSHADER_irBinOpType op, + const MOJOSHADER_astExpressionBinary *ast) +{ + MOJOSHADER_irExpression *lvalue = build_ir_expr(ctx, ast->left); + MOJOSHADER_irExpression *rvalue = build_ir_expr(ctx, ast->right); + const MOJOSHADER_astDataTypeType type = lvalue->info.type; + const int elems = lvalue->info.elements; + const int tmp = generate_ir_temp(ctx); + + // Semantic analysis should have inserted casts if necessary. + assert(type == rvalue->info.type); + assert(elems == rvalue->info.elements); + + // The destination must eventually be lvalue, which means memory or temp. + MOJOSHADER_irExpression *dst = lvalue; + while (dst->ir.type == MOJOSHADER_IR_ESEQ) + dst = dst->eseq.expr; + + if (dst->ir.type == MOJOSHADER_IR_TEMP) + dst = new_ir_temp(ctx, dst->temp.index, dst->info.type, dst->info.elements); + else if (dst->ir.type == MOJOSHADER_IR_MEMORY) + dst = new_ir_memory(ctx, dst->memory.index, dst->info.type, dst->info.elements); + else + assert(0 && "Unexpected condition"); + + // !!! FIXME: write masking! + return new_ir_eseq(ctx, + new_ir_seq(ctx, + new_ir_move(ctx, new_ir_temp(ctx, tmp, type, elems), new_ir_binop(ctx, op, lvalue, rvalue), -1), + new_ir_move(ctx, dst, new_ir_temp(ctx, tmp, type, elems), -1)), + new_ir_temp(ctx, tmp, type, elems)); +} // build_ir_assign_binop + +static MOJOSHADER_irExpression *build_ir_assign(Context *ctx, + const MOJOSHADER_astExpressionBinary *ast) +{ + MOJOSHADER_irExpression *lvalue = build_ir_expr(ctx, ast->left); + MOJOSHADER_irExpression *rvalue = build_ir_expr(ctx, ast->right); + const MOJOSHADER_astDataTypeType type = lvalue->info.type; + const int elems = lvalue->info.elements; + const int tmp = generate_ir_temp(ctx); + + // Semantic analysis should have inserted casts if necessary. + assert(type == rvalue->info.type); + assert(elems == rvalue->info.elements); + + // !!! FIXME: write masking! + // !!! FIXME: whole array/struct assignments need to become a sequence of moves. + return new_ir_eseq(ctx, + new_ir_seq(ctx, + new_ir_move(ctx, new_ir_temp(ctx, tmp, type, elems), rvalue, -1), + new_ir_move(ctx, lvalue, new_ir_temp(ctx, tmp, type, elems), -1)), + new_ir_temp(ctx, tmp, type, elems)); +} // build_ir_assign + + +// The AST must be perfect and normalized and sane here. If there are any +// strange corner cases, you should strive to handle them in semantic +// analysis, so conversion to IR can proceed with a minimum of drama. +static void *build_ir_internal(Context *ctx, void *_ast); +static inline MOJOSHADER_irNode *build_ir(Context *ctx, void *_ast) +{ + return (MOJOSHADER_irNode *) build_ir_internal(ctx, _ast); +} // build_ir + +static void *build_ir_internal(Context *ctx, void *_ast) +{ + if ((_ast == NULL) || (ctx->out_of_memory)) + return NULL; + + MOJOSHADER_astNode *ast = (MOJOSHADER_astNode *) _ast; + + // 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_PREINCREMENT: // !!! FIXME: sequence points? + return build_ir_preincdec(ctx, &ast->unary, MOJOSHADER_IR_BINOP_ADD); + + case MOJOSHADER_AST_OP_POSTINCREMENT: // !!! FIXME: sequence points? + return build_ir_postincdec(ctx, &ast->unary, MOJOSHADER_IR_BINOP_ADD); + + case MOJOSHADER_AST_OP_PREDECREMENT: // !!! FIXME: sequence points? + return build_ir_preincdec(ctx, &ast->unary, MOJOSHADER_IR_BINOP_SUBTRACT); + + case MOJOSHADER_AST_OP_POSTDECREMENT: // !!! FIXME: sequence points? + return build_ir_postincdec(ctx, &ast->unary, MOJOSHADER_IR_BINOP_SUBTRACT); + + case MOJOSHADER_AST_OP_COMPLEMENT: + return NEW_IR_BINOP(XOR, build_ir_expr(ctx, ast->unary.operand), + new_ir_constint(ctx, 0xFFFFFFFF)); + + case MOJOSHADER_AST_OP_NEGATE: // !!! FIXME: -0.0f != +0.0f + return NEW_IR_BINOP(SUBTRACT, build_ir_increxpr(ctx, ast->unary.datatype, -1), + build_ir_expr(ctx, ast->unary.operand)); + + case MOJOSHADER_AST_OP_NOT: // operand must be bool here! + assert(ast->unary.operand->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL); + return NEW_IR_BINOP(XOR, build_ir_expr(ctx, ast->unary.operand), + new_ir_constint(ctx, 1)); + + case MOJOSHADER_AST_OP_DEREF_ARRAY: + return build_ir_derefarray(ctx, &ast->binary); + + case MOJOSHADER_AST_OP_DEREF_STRUCT: + if (ast->derefstruct.isswizzle) + return build_ir_swizzle(ctx, &ast->derefstruct); + return build_ir_derefstruct(ctx, &ast->derefstruct); + + case MOJOSHADER_AST_OP_COMMA: + // evaluate and throw away left, return right. + return new_ir_eseq(ctx, new_ir_expr_stmt(ctx, build_ir_expr(ctx, ast->binary.left)), + build_ir_expr(ctx, ast->binary.right)); + + case MOJOSHADER_AST_OP_LESSTHAN: return EASY_IR_COMPARE(LT); + case MOJOSHADER_AST_OP_GREATERTHAN: return EASY_IR_COMPARE(GT); + case MOJOSHADER_AST_OP_LESSTHANOREQUAL: return EASY_IR_COMPARE(LEQ); + case MOJOSHADER_AST_OP_GREATERTHANOREQUAL: return EASY_IR_COMPARE(GEQ); + case MOJOSHADER_AST_OP_NOTEQUAL: return EASY_IR_COMPARE(NEQ); + case MOJOSHADER_AST_OP_EQUAL: return EASY_IR_COMPARE(EQL); + + case MOJOSHADER_AST_OP_MULTIPLY: return EASY_IR_BINOP(MULTIPLY); + case MOJOSHADER_AST_OP_DIVIDE: return EASY_IR_BINOP(DIVIDE); + case MOJOSHADER_AST_OP_MODULO: return EASY_IR_BINOP(MODULO); + case MOJOSHADER_AST_OP_ADD: return EASY_IR_BINOP(ADD); + case MOJOSHADER_AST_OP_SUBTRACT: return EASY_IR_BINOP(SUBTRACT); + case MOJOSHADER_AST_OP_LSHIFT: return EASY_IR_BINOP(LSHIFT); + case MOJOSHADER_AST_OP_RSHIFT: return EASY_IR_BINOP(RSHIFT); + case MOJOSHADER_AST_OP_BINARYAND: return EASY_IR_BINOP(AND); + case MOJOSHADER_AST_OP_BINARYXOR: return EASY_IR_BINOP(XOR); + case MOJOSHADER_AST_OP_BINARYOR: return EASY_IR_BINOP(OR); + + case MOJOSHADER_AST_OP_LOGICALAND: + return build_ir_logical_and(ctx, &ast->binary); + + case MOJOSHADER_AST_OP_LOGICALOR: + return build_ir_logical_or(ctx, &ast->binary); + + case MOJOSHADER_AST_OP_ASSIGN: + return build_ir_assign(ctx, &ast->binary); + + case MOJOSHADER_AST_OP_MULASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_MULTIPLY, &ast->binary); + case MOJOSHADER_AST_OP_DIVASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_DIVIDE, &ast->binary); + case MOJOSHADER_AST_OP_MODASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_MODULO, &ast->binary); + case MOJOSHADER_AST_OP_ADDASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_ADD, &ast->binary); + case MOJOSHADER_AST_OP_SUBASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_SUBTRACT, &ast->binary); + case MOJOSHADER_AST_OP_LSHIFTASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_LSHIFT, &ast->binary); + case MOJOSHADER_AST_OP_RSHIFTASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_RSHIFT, &ast->binary); + case MOJOSHADER_AST_OP_ANDASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_AND, &ast->binary); + case MOJOSHADER_AST_OP_XORASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_XOR, &ast->binary); + case MOJOSHADER_AST_OP_ORASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_OR, &ast->binary); + + case MOJOSHADER_AST_OP_CONDITIONAL: + assert(ast->binary.left->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL); + return build_ir_compare(ctx, MOJOSHADER_IR_COND_EQL, + build_ir_expr(ctx, ast->ternary.left), + new_ir_constbool(ctx, 1), + build_ir_expr(ctx, ast->ternary.center), + build_ir_expr(ctx, ast->ternary.right)); + + case MOJOSHADER_AST_OP_IDENTIFIER: + return build_ir_identifier(ctx, &ast->identifier); + + case MOJOSHADER_AST_OP_INT_LITERAL: + return new_ir_constint(ctx, ast->intliteral.value); + + case MOJOSHADER_AST_OP_FLOAT_LITERAL: + return new_ir_constfloat(ctx, ast->floatliteral.value); + + case MOJOSHADER_AST_OP_BOOLEAN_LITERAL: + return new_ir_constbool(ctx, ast->boolliteral.value); + + case MOJOSHADER_AST_OP_CALLFUNC: + return build_ir_call(ctx, &ast->callfunc); + + case MOJOSHADER_AST_OP_CONSTRUCTOR: + return build_ir_constructor(ctx, &ast->constructor); + + case MOJOSHADER_AST_OP_CAST: + return build_ir_convert(ctx, &ast->cast); + + case MOJOSHADER_AST_STATEMENT_BREAK: + { + const LoopLabels *labels = ctx->ir_loop; + assert(labels != NULL); // semantic analysis should catch this. + return new_ir_jump(ctx, labels->end); + } // case + + case MOJOSHADER_AST_STATEMENT_CONTINUE: + { + const LoopLabels *labels = ctx->ir_loop; + assert(labels != NULL); // semantic analysis should catch this. + return new_ir_jump(ctx, labels->start); + } // case + + case MOJOSHADER_AST_STATEMENT_DISCARD: + return new_ir_seq(ctx, new_ir_discard(ctx), build_ir_stmt(ctx, ast->discardstmt.next)); + + case MOJOSHADER_AST_STATEMENT_EMPTY: + return build_ir(ctx, ast->stmt.next); // skip it, do next thing. + + case MOJOSHADER_AST_STATEMENT_EXPRESSION: + return new_ir_seq(ctx, new_ir_expr_stmt(ctx, build_ir_expr(ctx, ast->exprstmt.expr)), build_ir_stmt(ctx, ast->exprstmt.next)); + + case MOJOSHADER_AST_STATEMENT_IF: + return build_ir_ifstmt(ctx, &ast->ifstmt); + + case MOJOSHADER_AST_STATEMENT_TYPEDEF: // ignore this, move on. + return build_ir(ctx, ast->typedefstmt.next); + + case MOJOSHADER_AST_STATEMENT_SWITCH: + return build_ir_switch(ctx, &ast->switchstmt); + + case MOJOSHADER_AST_STATEMENT_STRUCT: // ignore this, move on. + return build_ir(ctx, ast->structstmt.next); + + case MOJOSHADER_AST_STATEMENT_VARDECL: // ignore this, move on. + return build_ir(ctx, ast->vardeclstmt.next); + + case MOJOSHADER_AST_STATEMENT_BLOCK: + return new_ir_seq(ctx, build_ir_stmt(ctx, ast->blockstmt.statements), build_ir_stmt(ctx, ast->blockstmt.next)); + + case MOJOSHADER_AST_STATEMENT_FOR: + return build_ir_forstmt(ctx, &ast->forstmt); + + case MOJOSHADER_AST_STATEMENT_DO: + return build_ir_dostmt(ctx, &ast->dostmt); + + case MOJOSHADER_AST_STATEMENT_WHILE: + return build_ir_whilestmt(ctx, &ast->whilestmt); + + case MOJOSHADER_AST_STATEMENT_RETURN: + { + const int label = ctx->ir_end; + assert(label >= 0); // parser should have caught this! + MOJOSHADER_irStatement *retval = NULL; + if (ast->returnstmt.expr != NULL) + { + // !!! FIXME: whole array/struct returns need to move more into the temp. + const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->returnstmt.expr->datatype); + const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type; + const int elems = datatype_elems(ctx, dt); + assert(ctx->ir_ret >= 0); + retval = new_ir_move(ctx, new_ir_temp(ctx, ctx->ir_ret, type, elems), build_ir_expr(ctx, ast->returnstmt.expr), -1); + } // if + return new_ir_seq(ctx, retval, new_ir_jump(ctx, label)); + } // case + + case MOJOSHADER_AST_COMPUNIT_TYPEDEF: + case MOJOSHADER_AST_COMPUNIT_STRUCT: + case MOJOSHADER_AST_COMPUNIT_VARIABLE: + case MOJOSHADER_AST_COMPUNIT_FUNCTION: + case MOJOSHADER_AST_ARGUMENTS: + case MOJOSHADER_AST_OP_STRING_LITERAL: + case MOJOSHADER_AST_SWITCH_CASE: + case MOJOSHADER_AST_SCALAR_OR_ARRAY: + case MOJOSHADER_AST_TYPEDEF: + case MOJOSHADER_AST_FUNCTION_PARAMS: + case MOJOSHADER_AST_FUNCTION_SIGNATURE: + case MOJOSHADER_AST_STRUCT_DECLARATION: + case MOJOSHADER_AST_STRUCT_MEMBER: + case MOJOSHADER_AST_VARIABLE_DECLARATION: + case MOJOSHADER_AST_ANNOTATION: + case MOJOSHADER_AST_PACK_OFFSET: + case MOJOSHADER_AST_VARIABLE_LOWLEVEL: + assert(0 && "Shouldn't hit this in build_ir."); + return NULL; + + default: + assert(0 && "unexpected type"); + return NULL; + } // switch +} // build_ir + +static void print_ir(FILE *io, unsigned int depth, void *_ir) +{ + MOJOSHADER_irNode *ir = (MOJOSHADER_irNode *) _ir; + if (ir == NULL) + return; + + const char *fname = strrchr(ir->ir.filename, '/'); + if (fname != NULL) + fname++; + else + { + fname = strrchr(ir->ir.filename, '\\'); + if (fname != NULL) + fname++; + else + fname = ir->ir.filename; + } // else + + int i; + for (i = 0; i < depth; i++) + fprintf(io, " "); + depth++; + + fprintf(io, "[ %s:%d ", fname, ir->ir.line); + + switch (ir->ir.type) + { + case MOJOSHADER_IR_LABEL: + fprintf(io, "LABEL %d ]\n", ir->stmt.label.index); + break; + + case MOJOSHADER_IR_CONSTANT: + fprintf(io, "CONSTANT "); + switch (ir->expr.constant.info.type) + { + case MOJOSHADER_AST_DATATYPE_BOOL: + case MOJOSHADER_AST_DATATYPE_INT: + case MOJOSHADER_AST_DATATYPE_UINT: + for (i = 0; i < ir->expr.constant.info.elements-1; i++) + fprintf(io, "%d, ", ir->expr.constant.value.ival[i]); + if (ir->expr.constant.info.elements > 0) + fprintf(io, "%d", ir->expr.constant.value.ival[i]); + break; + + 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: + for (i = 0; i < ir->expr.constant.info.elements-1; i++) + fprintf(io, "%ff, ", ir->expr.constant.value.fval[i]); + if (ir->expr.constant.info.elements > 0) + fprintf(io, "%ff", ir->expr.constant.value.fval[i]); + break; + + default: assert(0 && "shouldn't happen"); + } // switch + fprintf(io, " ]\n"); + break; + + case MOJOSHADER_IR_TEMP: + fprintf(io, "TEMP %d ]\n", ir->expr.temp.index); + break; + + case MOJOSHADER_IR_DISCARD: + fprintf(io, "DISCARD ]\n"); + break; + + case MOJOSHADER_IR_SWIZZLE: + fprintf(io, "SWIZZLE"); + for (i = 0; i < ir->expr.swizzle.info.elements-1; i++) + fprintf(io, " %d", (int) ir->expr.swizzle.channels[i]); + fprintf(io, " ]\n"); + print_ir(io, depth, ir->expr.swizzle.expr); + break; + + case MOJOSHADER_IR_CONSTRUCT: + fprintf(io, "CONSTRUCT ]\n"); + print_ir(io, depth, ir->expr.construct.args); + break; + + case MOJOSHADER_IR_CONVERT: + fprintf(io, "CONVERT ]\n"); + print_ir(io, depth, ir->expr.convert.expr); + break; + + case MOJOSHADER_IR_BINOP: + fprintf(io, "BINOP "); + switch (ir->expr.binop.op) + { + #define PRINT_IR_BINOP(x) \ + case MOJOSHADER_IR_BINOP_##x: fprintf(io, #x); break; + PRINT_IR_BINOP(ADD) + PRINT_IR_BINOP(SUBTRACT) + PRINT_IR_BINOP(MULTIPLY) + PRINT_IR_BINOP(DIVIDE) + PRINT_IR_BINOP(MODULO) + PRINT_IR_BINOP(AND) + PRINT_IR_BINOP(OR) + PRINT_IR_BINOP(XOR) + PRINT_IR_BINOP(LSHIFT) + PRINT_IR_BINOP(RSHIFT) + PRINT_IR_BINOP(UNKNOWN) + #undef PRINT_IR_BINOP + default: assert(0 && "unexpected case"); break; + } // switch + fprintf(io, " ]\n"); + print_ir(io, depth, ir->expr.binop.left); + print_ir(io, depth, ir->expr.binop.right); + break; + + case MOJOSHADER_IR_MEMORY: + fprintf(io, "MEMORY %d ]\n", ir->expr.memory.index); + break; + + case MOJOSHADER_IR_CALL: + fprintf(io, "CALL %d ]\n", ir->expr.call.index); + print_ir(io, depth, ir->expr.call.args); + break; + + case MOJOSHADER_IR_ESEQ: + fprintf(io, "ESEQ ]\n"); + print_ir(io, depth, ir->expr.eseq.stmt); + break; + + case MOJOSHADER_IR_ARRAY: + fprintf(io, "ARRAY ]\n"); + print_ir(io, depth, ir->expr.array.array); + print_ir(io, depth, ir->expr.array.element); + break; + + case MOJOSHADER_IR_MOVE: + fprintf(io, "MOVE ]\n"); + print_ir(io, depth, ir->stmt.move.dst); + print_ir(io, depth, ir->stmt.move.src); + break; + + case MOJOSHADER_IR_EXPR_STMT: + fprintf(io, "EXPRSTMT ]\n"); + print_ir(io, depth, ir->stmt.expr.expr); + break; + + case MOJOSHADER_IR_JUMP: + fprintf(io, "JUMP %d ]\n", ir->stmt.jump.label); + break; + + case MOJOSHADER_IR_CJUMP: + fprintf(io, "CJUMP "); + switch (ir->stmt.cjump.cond) + { + #define PRINT_IR_COND(x) \ + case MOJOSHADER_IR_COND_##x: fprintf(io, #x); break; + PRINT_IR_COND(EQL) + PRINT_IR_COND(NEQ) + PRINT_IR_COND(LT) + PRINT_IR_COND(GT) + PRINT_IR_COND(LEQ) + PRINT_IR_COND(GEQ) + PRINT_IR_COND(UNKNOWN) + #undef PRINT_IR_COND + default: assert(0 && "unexpected case"); break; + } // switch + fprintf(io, " %d %d ]\n", ir->stmt.cjump.iftrue, ir->stmt.cjump.iffalse); + print_ir(io, depth, ir->stmt.cjump.left); + print_ir(io, depth, ir->stmt.cjump.right); + break; + + case MOJOSHADER_IR_SEQ: + fprintf(io, "SEQ ]\n"); + print_ir(io, depth, ir->stmt.seq.first); + print_ir(io, depth, ir->stmt.seq.next); // !!! FIXME: don't recurse? + break; + + case MOJOSHADER_IR_EXPRLIST: + fprintf(io, "EXPRLIST ]\n"); + print_ir(io, depth, ir->misc.exprlist.expr); + print_ir(io, depth, ir->misc.exprlist.next); // !!! FIXME: don't recurse? + break; + + default: assert(0 && "unexpected IR node"); break; + } // switch +} // print_ir + +static void print_whole_ir(Context *ctx, FILE *io) +{ + if (ctx->ir != NULL) + { + int i; + for (i = 0; i <= ctx->user_func_index; i++) + { + printf("[FUNCTION %d ]\n", i); + print_ir(io, 1, ctx->ir[i]); + } // for + } // if +} // print_whole_ir + +static void delete_ir(Context *ctx, void *_ir) +{ + MOJOSHADER_irNode *ir = (MOJOSHADER_irNode *) _ir; + if (ir == NULL) + return; + + switch (ir->ir.type) + { + case MOJOSHADER_IR_JUMP: + case MOJOSHADER_IR_LABEL: + case MOJOSHADER_IR_CONSTANT: + case MOJOSHADER_IR_TEMP: + case MOJOSHADER_IR_DISCARD: + case MOJOSHADER_IR_MEMORY: + break; // nothing extra to free here. + + case MOJOSHADER_IR_BINOP: + delete_ir(ctx, ir->expr.binop.left); + delete_ir(ctx, ir->expr.binop.right); + break; + + case MOJOSHADER_IR_CALL: + delete_ir(ctx, ir->expr.call.args); + break; + + case MOJOSHADER_IR_ESEQ: + delete_ir(ctx, ir->expr.eseq.stmt); + delete_ir(ctx, ir->expr.eseq.expr); + break; + + case MOJOSHADER_IR_ARRAY: + delete_ir(ctx, ir->expr.array.array); + delete_ir(ctx, ir->expr.array.element); + break; + + case MOJOSHADER_IR_MOVE: + delete_ir(ctx, ir->stmt.move.dst); + delete_ir(ctx, ir->stmt.move.src); + break; + + case MOJOSHADER_IR_EXPR_STMT: + delete_ir(ctx, ir->stmt.expr.expr); + break; + + case MOJOSHADER_IR_CJUMP: + delete_ir(ctx, ir->stmt.cjump.left); + delete_ir(ctx, ir->stmt.cjump.right); + break; + + case MOJOSHADER_IR_SEQ: + delete_ir(ctx, ir->stmt.seq.first); + delete_ir(ctx, ir->stmt.seq.next); // !!! FIXME: don't recurse? + break; + + case MOJOSHADER_IR_EXPRLIST: + delete_ir(ctx, ir->misc.exprlist.expr); + delete_ir(ctx, ir->misc.exprlist.next); // !!! FIXME: don't recurse? + break; + + case MOJOSHADER_IR_SWIZZLE: + delete_ir(ctx, ir->expr.swizzle.expr); + break; + + case MOJOSHADER_IR_CONSTRUCT: + delete_ir(ctx, ir->expr.construct.args); + break; + + case MOJOSHADER_IR_CONVERT: + delete_ir(ctx, ir->expr.convert.expr); + break; + + default: assert(0 && "unexpected IR node"); break; + } // switch + + Free(ctx, ir); +} // delete_ir + +static void intermediate_representation(Context *ctx) +{ + const MOJOSHADER_astCompilationUnit *ast = NULL; + const MOJOSHADER_astCompilationUnitFunction *astfn = NULL; + const size_t arraylen = (ctx->user_func_index+1) * sizeof (MOJOSHADER_irStatement *); + + ctx->ir = Malloc(ctx, arraylen); + if (ctx->ir == NULL) + return; + memset(ctx->ir, '\0', arraylen); + + ctx->ir_end = -1; + ctx->ir_ret = -1; + + for (ast = &ctx->ast->compunit; ast != NULL; ast = ast->next) + { + assert(ast->ast.type > MOJOSHADER_AST_COMPUNIT_START_RANGE); + assert(ast->ast.type < MOJOSHADER_AST_COMPUNIT_END_RANGE); + if (ast->ast.type != MOJOSHADER_AST_COMPUNIT_FUNCTION) + continue; // only care about functions right now. + + astfn = (MOJOSHADER_astCompilationUnitFunction *) ast; + if (astfn->definition == NULL) // just a predeclare; skip. + continue; + + assert(ctx->ir_loop == NULL); // parser should have caught this! + assert(ctx->ir_end < 0); // parser should have caught this! + assert(ctx->ir_ret < 0); // parser should have caught this! + const int start = generate_ir_label(ctx); // !!! FIXME: store somewhere. + const int end = generate_ir_label(ctx); + ctx->ir_end = end; + + if (astfn->declaration->datatype != NULL) + ctx->ir_ret = generate_ir_temp(ctx); + + MOJOSHADER_irStatement *funcseq = new_ir_seq(ctx, new_ir_label(ctx, start), build_ir_stmt(ctx, astfn->definition)); + funcseq = new_ir_seq(ctx, funcseq, new_ir_label(ctx, end)); + assert(ctx->ir_loop == NULL); // parser should have caught this! + ctx->ir_end = -1; + ctx->ir_ret = -1; + + assert(astfn->index <= ctx->user_func_index); + assert(ctx->ir[astfn->index] == NULL); + ctx->ir[astfn->index] = funcseq; + } // for + + print_whole_ir(ctx, stdout); + + // done with the AST, nuke it. + // !!! FIXME: we're going to need CTAB data from this at some point. + delete_compilation_unit(ctx, (MOJOSHADER_astCompilationUnit *) ctx->ast); + ctx->ast = NULL; +} // intermediate_representation + + + +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) +{ + ctx->source_profile = srcprofile; + + #define TEST_PROFILE(x) if (strcmp(srcprofile, x) == 0) { return; } + + 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)) + intermediate_representation(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); + // !!! FIXME: this is missing stuff (including freeing substructs). + } // for + f((void *) data->symbols, d); + + f((void *) data->output, d); + f(data, d); +} // MOJOSHADER_freeCompileData + +// end of mojoshader_compiler.c ... + |