/* * Copyright © 2016 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /** * \file lower_int64.cpp * * Lower 64-bit operations to 32-bit operations. Each 64-bit value is lowered * to a uvec2. For each operation that can be lowered, there is a function * called __builtin_foo with the same number of parameters that takes uvec2 * sources and produces uvec2 results. An operation like * * uint64_t(x) * uint64_t(y) * * becomes * * packUint2x32(__builtin_umul64(unpackUint2x32(x), unpackUint2x32(y))); */ #include "main/macros.h" #include "compiler/glsl_types.h" #include "ir.h" #include "ir_rvalue_visitor.h" #include "ir_builder.h" #include "ir_optimization.h" #include "util/hash_table.h" #include "builtin_functions.h" typedef ir_function_signature *(*function_generator)(void *mem_ctx, builtin_available_predicate avail); using namespace ir_builder; namespace lower_64bit { void expand_source(ir_factory &, ir_rvalue *val, ir_variable **expanded_src); ir_dereference_variable *compact_destination(ir_factory &, const glsl_type *type, ir_variable *result[4]); ir_rvalue *lower_op_to_function_call(ir_instruction *base_ir, ir_expression *ir, ir_function_signature *callee); }; using namespace lower_64bit; namespace { class lower_64bit_visitor : public ir_rvalue_visitor { public: lower_64bit_visitor(void *mem_ctx, exec_list *instructions, unsigned lower) : progress(false), lower(lower), instructions(instructions), function_list(), added_functions(&function_list, mem_ctx) { functions = _mesa_hash_table_create(mem_ctx, _mesa_key_hash_string, _mesa_key_string_equal); foreach_in_list(ir_instruction, node, instructions) { ir_function *const f = node->as_function(); if (f == NULL || strncmp(f->name, "__builtin_", 10) != 0) continue; add_function(f); } } ~lower_64bit_visitor() { _mesa_hash_table_destroy(functions, NULL); } void handle_rvalue(ir_rvalue **rvalue); void add_function(ir_function *f) { _mesa_hash_table_insert(functions, f->name, f); } ir_function *find_function(const char *name) { struct hash_entry *const entry = _mesa_hash_table_search(functions, name); return entry != NULL ? (ir_function *) entry->data : NULL; } bool progress; private: unsigned lower; /** Bitfield of which operations to lower */ exec_list *instructions; /** Hashtable containing all of the known functions in the IR */ struct hash_table *functions; public: exec_list function_list; private: ir_factory added_functions; ir_rvalue *handle_op(ir_expression *ir, const char *function_name, function_generator generator); }; } /* anonymous namespace */ static bool is_integer_64(const glsl_type *t) { return t->base_type == GLSL_TYPE_UINT64 || t->base_type == GLSL_TYPE_INT64; } /** * Determine if a particular type of lowering should occur */ #define lowering(x) (this->lower & x) bool lower_64bit_integer_instructions(exec_list *instructions, unsigned what_to_lower) { if (instructions->is_empty()) return false; ir_instruction *first_inst = (ir_instruction *) instructions->get_head_raw(); void *const mem_ctx = ralloc_parent(first_inst); lower_64bit_visitor v(mem_ctx, instructions, what_to_lower); visit_list_elements(&v, instructions); if (v.progress && !v.function_list.is_empty()) { /* Move all of the nodes from function_list to the head if the incoming * instruction list. */ exec_node *const after = &instructions->head_sentinel; exec_node *const before = instructions->head_sentinel.next; exec_node *const head = v.function_list.head_sentinel.next; exec_node *const tail = v.function_list.tail_sentinel.prev; before->next = head; head->prev = before; after->prev = tail; tail->next = after; } return v.progress; } /** * Expand individual 64-bit values to uvec2 values * * Each operation is in one of a few forms. * * vector op vector * vector op scalar * scalar op vector * scalar op scalar * * In the 'vector op vector' case, the two vectors must have the same size. * In a way, the 'scalar op scalar' form is special case of the 'vector op * vector' form. * * This method generates a new set of uvec2 values for each element of a * single operand. If the operand is a scalar, the uvec2 is replicated * multiple times. A value like * * u64vec3(a) + u64vec3(b) * * becomes * * u64vec3 tmp0 = u64vec3(a) + u64vec3(b); * uvec2 tmp1 = unpackUint2x32(tmp0.x); * uvec2 tmp2 = unpackUint2x32(tmp0.y); * uvec2 tmp3 = unpackUint2x32(tmp0.z); * * and the returned operands array contains ir_variable pointers to * * { tmp1, tmp2, tmp3, tmp1 } */ void lower_64bit::expand_source(ir_factory &body, ir_rvalue *val, ir_variable **expanded_src) { assert(val->type->base_type == GLSL_TYPE_UINT64 || val->type->base_type == GLSL_TYPE_INT64); ir_variable *const temp = body.make_temp(val->type, "tmp"); body.emit(assign(temp, val)); const ir_expression_operation unpack_opcode = val->type->base_type == GLSL_TYPE_UINT64 ? ir_unop_unpack_uint_2x32 : ir_unop_unpack_int_2x32; const glsl_type *const type = val->type->base_type == GLSL_TYPE_UINT64 ? glsl_type::uvec2_type : glsl_type::ivec2_type; unsigned i; for (i = 0; i < val->type->vector_elements; i++) { expanded_src[i] = body.make_temp(type, "expanded_64bit_source"); body.emit(assign(expanded_src[i], expr(unpack_opcode, swizzle(temp, i, 1)))); } for (/* empty */; i < 4; i++) expanded_src[i] = expanded_src[0]; } /** * Convert a series of uvec2 results into a single 64-bit integer vector */ ir_dereference_variable * lower_64bit::compact_destination(ir_factory &body, const glsl_type *type, ir_variable *result[4]) { const ir_expression_operation pack_opcode = type->base_type == GLSL_TYPE_UINT64 ? ir_unop_pack_uint_2x32 : ir_unop_pack_int_2x32; ir_variable *const compacted_result = body.make_temp(type, "compacted_64bit_result"); for (unsigned i = 0; i < type->vector_elements; i++) { body.emit(assign(compacted_result, expr(pack_opcode, result[i]), 1U << i)); } void *const mem_ctx = ralloc_parent(compacted_result); return new(mem_ctx) ir_dereference_variable(compacted_result); } ir_rvalue * lower_64bit::lower_op_to_function_call(ir_instruction *base_ir, ir_expression *ir, ir_function_signature *callee) { const unsigned num_operands = ir->get_num_operands(); ir_variable *src[4][4]; ir_variable *dst[4]; void *const mem_ctx = ralloc_parent(ir); exec_list instructions; unsigned source_components = 0; const glsl_type *const result_type = ir->type->base_type == GLSL_TYPE_UINT64 ? glsl_type::uvec2_type : glsl_type::ivec2_type; ir_factory body(&instructions, mem_ctx); for (unsigned i = 0; i < num_operands; i++) { expand_source(body, ir->operands[i], src[i]); if (ir->operands[i]->type->vector_elements > source_components) source_components = ir->operands[i]->type->vector_elements; } for (unsigned i = 0; i < source_components; i++) { dst[i] = body.make_temp(result_type, "expanded_64bit_result"); exec_list parameters; for (unsigned j = 0; j < num_operands; j++) parameters.push_tail(new(mem_ctx) ir_dereference_variable(src[j][i])); ir_dereference_variable *const return_deref = new(mem_ctx) ir_dereference_variable(dst[i]); ir_call *const c = new(mem_ctx) ir_call(callee, return_deref, ¶meters); body.emit(c); } ir_rvalue *const rv = compact_destination(body, ir->type, dst); /* Move all of the nodes from instructions between base_ir and the * instruction before it. */ exec_node *const after = base_ir; exec_node *const before = after->prev; exec_node *const head = instructions.head_sentinel.next; exec_node *const tail = instructions.tail_sentinel.prev; before->next = head; head->prev = before; after->prev = tail; tail->next = after; return rv; } ir_rvalue * lower_64bit_visitor::handle_op(ir_expression *ir, const char *function_name, function_generator generator) { for (unsigned i = 0; i < ir->get_num_operands(); i++) if (!is_integer_64(ir->operands[i]->type)) return ir; /* Get a handle to the correct ir_function_signature for the core * operation. */ ir_function_signature *callee = NULL; ir_function *f = find_function(function_name); if (f != NULL) { callee = (ir_function_signature *) f->signatures.get_head(); assert(callee != NULL && callee->ir_type == ir_type_function_signature); } else { f = new(base_ir) ir_function(function_name); callee = generator(base_ir, NULL); f->add_signature(callee); add_function(f); } return lower_op_to_function_call(this->base_ir, ir, callee); } void lower_64bit_visitor::handle_rvalue(ir_rvalue **rvalue) { if (*rvalue == NULL || (*rvalue)->ir_type != ir_type_expression) return; ir_expression *const ir = (*rvalue)->as_expression(); assert(ir != NULL); switch (ir->operation) { case ir_unop_sign: if (lowering(SIGN64)) { *rvalue = handle_op(ir, "__builtin_sign64", generate_ir::sign64); this->progress = true; } break; case ir_binop_div: if (lowering(DIV64)) { if (ir->type->base_type == GLSL_TYPE_UINT64) { *rvalue = handle_op(ir, "__builtin_udiv64", generate_ir::udiv64); } else { *rvalue = handle_op(ir, "__builtin_idiv64", generate_ir::idiv64); } this->progress = true; } break; case ir_binop_mod: if (lowering(MOD64)) { if (ir->type->base_type == GLSL_TYPE_UINT64) { *rvalue = handle_op(ir, "__builtin_umod64", generate_ir::umod64); } else { *rvalue = handle_op(ir, "__builtin_imod64", generate_ir::imod64); } this->progress = true; } break; case ir_binop_mul: if (lowering(MUL64)) { *rvalue = handle_op(ir, "__builtin_umul64", generate_ir::umul64); this->progress = true; } break; default: break; } }