Commit 89398b65 by Olli Etuaho Committed by Commit Bot

Avoid mangled name comparisons of 3-parameter functions

The hash values used for looking up built-ins now encode whether the mangled name contains arrays, structs or interface blocks in its parameters list. This is written in the most significant bit of the hash value. We check this bit at the start of built-in lookup and if the bit is set we exit early. After that we know that the lookup name doesn't contain array, struct or interface block parameters. When we find a hash that matches a hash of a built-in function, we now know 3 things: 1) the length of the mangled name matches 2) the open parentheses in the mangled name matches 3) the lookup doesn't contain array, struct or block parameters. Additionally, we have an if statement checking whether the function name matches. Collisions are only possible with functions that 1) have the same name 2) have the same number of parameters With these preconditions we can check beforehand whether collisions are possible for 3-parameter functions. If there are no collisions, we don't need to compare the full mangled name. This is similar to what was already being done with functions that had 0 to 2 parameters. This reduces shader_translator binary size by around 4 KB on Windows. Besides increased complexity, the tradeoff is that an exhaustive search of hash values for possible 3-parameter combinations is costly, so the gen_builtin_functions.py code generation script now takes around one minute to run on a high-end workstation. Due to this, the script now exits early if it detects it has already been run with the same inputs based on a hash value stored in builtin_symbols_hash_autogen.txt. BUG=angleproject:2267 BUG=chromium:823856 TEST=angle_unittests Change-Id: I3ff8c6eb85b90d3c4971ac8d73ee171a07a7e55f Reviewed-on: https://chromium-review.googlesource.com/973372Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
parent 5d232d53
...@@ -163,6 +163,7 @@ generators = { ...@@ -163,6 +163,7 @@ generators = {
], ],
'outputs': [ 'outputs': [
'src/compiler/translator/tree_util/BuiltIn_autogen.h', 'src/compiler/translator/tree_util/BuiltIn_autogen.h',
'src/compiler/translator/builtin_symbols_hash_autogen.txt',
'src/compiler/translator/ParseContext_autogen.h', 'src/compiler/translator/ParseContext_autogen.h',
'src/compiler/translator/SymbolTable_autogen.cpp', 'src/compiler/translator/SymbolTable_autogen.cpp',
'src/compiler/translator/SymbolTable_autogen.h', 'src/compiler/translator/SymbolTable_autogen.h',
......
...@@ -37,11 +37,12 @@ const size_t ImmutableString::FowlerNollVoHash<8>::kFnvOffsetBasis = ...@@ -37,11 +37,12 @@ const size_t ImmutableString::FowlerNollVoHash<8>::kFnvOffsetBasis =
uint32_t ImmutableString::mangledNameHash() const uint32_t ImmutableString::mangledNameHash() const
{ {
const char *dataPtr = data(); const char *dataPtr = data();
uint32_t hash = static_cast<uint32_t>(FowlerNollVoHash<4>::kFnvOffsetBasis); uint32_t hash = static_cast<uint32_t>(FowlerNollVoHash<4>::kFnvOffsetBasis);
const uint32_t kMaxSixBitValue = (1u << 6) - 1u; const uint32_t kMaxSixBitValue = (1u << 6) - 1u;
uint32_t parenLocation = kMaxSixBitValue; uint32_t parenLocation = kMaxSixBitValue;
uint32_t index = 0; uint32_t hasArrayOrBlockParamBit = 0u;
uint32_t index = 0;
while (dataPtr[index] != '\0') while (dataPtr[index] != '\0')
{ {
hash = hash ^ dataPtr[index]; hash = hash ^ dataPtr[index];
...@@ -53,11 +54,16 @@ uint32_t ImmutableString::mangledNameHash() const ...@@ -53,11 +54,16 @@ uint32_t ImmutableString::mangledNameHash() const
ASSERT(parenLocation == kMaxSixBitValue); ASSERT(parenLocation == kMaxSixBitValue);
parenLocation = index; parenLocation = index;
} }
else if (dataPtr[index] == '{' || dataPtr[index] == '[')
{
hasArrayOrBlockParamBit = 1u;
}
++index; ++index;
} }
// Should not be called with strings longer than 63 characters. // Should not be called with strings longer than 63 characters.
ASSERT(index <= kMaxSixBitValue); ASSERT(index <= kMaxSixBitValue);
return ((hash >> 12) ^ (hash & 0xfff)) | (parenLocation << 26) | (index << 20); return ((hash >> 13) ^ (hash & 0x1fff)) | (index << 19) | (parenLocation << 25) |
(hasArrayOrBlockParamBit << 31);
} }
} // namespace sh } // namespace sh
...@@ -128,9 +128,9 @@ class ImmutableString ...@@ -128,9 +128,9 @@ class ImmutableString
} }
}; };
// This hash encodes the opening parentheses location (if any) and name length in addition to a // This hash encodes the opening parentheses location (if any), name length and whether the name
// 20-bit hash. This way the hash is more useful for lookups. The string passed in should be at // contains { or [ characters in addition to a 19-bit hash. This way the hash is more useful for
// most 63 characters. // lookups. The string passed in should be at most 63 characters.
uint32_t mangledNameHash() const; uint32_t mangledNameHash() const;
private: private:
......
This source diff could not be displayed because it is too large. You can view the blob instead.
cc849405cb2fd922f9a269ab6594cb9b
\ No newline at end of file
...@@ -9,9 +9,11 @@ ...@@ -9,9 +9,11 @@
from collections import OrderedDict from collections import OrderedDict
from datetime import date from datetime import date
import argparse import argparse
import hashlib
import json import json
import re import re
import os import os
import sys
def set_working_dir(): def set_working_dir():
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
...@@ -19,6 +21,24 @@ def set_working_dir(): ...@@ -19,6 +21,24 @@ def set_working_dir():
set_working_dir() set_working_dir()
variables_json_filename = 'builtin_variables.json'
functions_txt_filename = 'builtin_function_declarations.txt'
hash_filename = 'builtin_symbols_hash_autogen.txt'
all_inputs = [os.path.abspath(__file__), variables_json_filename, functions_txt_filename]
# This script takes a while to run since it searches for hash collisions of mangled names. To avoid
# running it unnecessarily, we first check if we've already ran it with the same inputs.
m = hashlib.md5()
for input_path in all_inputs:
with open(input_path, 'rU') as input_file:
m.update(input_file.read())
input_hash = m.hexdigest()
if os.path.exists(hash_filename):
with open(hash_filename) as hash_file:
if input_hash == hash_file.read():
print "Canceling ESSL static builtins code generator - generated hash matches inputs."
sys.exit(0)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--dump-intermediate-json', help='Dump parsed function data as a JSON file builtin_functions.json', action="store_true") parser.add_argument('--dump-intermediate-json', help='Dump parsed function data as a JSON file builtin_functions.json', action="store_true")
args = parser.parse_args() args = parser.parse_args()
...@@ -207,6 +227,11 @@ const TSymbol *TSymbolTable::findBuiltIn(const ImmutableString &name, ...@@ -207,6 +227,11 @@ const TSymbol *TSymbolTable::findBuiltIn(const ImmutableString &name,
return nullptr; return nullptr;
}} }}
uint32_t nameHash = name.mangledNameHash(); uint32_t nameHash = name.mangledNameHash();
if ((nameHash >> 31) != 0)
{{
// The name contains [ or {{.
return nullptr;
}}
{get_builtin} {get_builtin}
}} }}
...@@ -255,9 +280,6 @@ namespace BuiltInGroup ...@@ -255,9 +280,6 @@ namespace BuiltInGroup
parsed_variables = None parsed_variables = None
variables_json_filename = 'builtin_variables.json'
functions_txt_filename = 'builtin_function_declarations.txt'
basic_types_enumeration = [ basic_types_enumeration = [
'Void', 'Void',
'Float', 'Float',
...@@ -541,35 +563,6 @@ class TType: ...@@ -541,35 +563,6 @@ class TType:
raise Exception('Unrecognized type: ' + str(glsl_header_type)) raise Exception('Unrecognized type: ' + str(glsl_header_type))
ttype_mangled_name_variants = []
for basic_type in basic_types_enumeration:
primary_sizes = [1]
secondary_sizes = [1]
if basic_type in ['Float', 'Int', 'UInt', 'Bool']:
primary_sizes = [1, 2, 3, 4]
if basic_type == 'Float':
secondary_sizes = [1, 2, 3, 4]
for primary_size in primary_sizes:
for secondary_size in secondary_sizes:
type = TType({'basic': basic_type, 'primarySize': primary_size, 'secondarySize': secondary_size})
ttype_mangled_name_variants.append(type.get_mangled_name())
def gen_parameters_mangled_name_variants(str_len):
if str_len % 2 != 0:
raise Exception('Expecting parameters mangled name length to be divisible by two')
num_variants = pow(len(ttype_mangled_name_variants), str_len / 2)
for variant_id in xrange(num_variants):
variant = ''
while (len(variant)) < str_len:
parameter_index = len(variant) / 2
parameter_variant_index = variant_id
for i in xrange(parameter_index):
parameter_variant_index = parameter_variant_index / len(ttype_mangled_name_variants)
parameter_variant_index = parameter_variant_index % len(ttype_mangled_name_variants)
variant += ttype_mangled_name_variants[parameter_variant_index]
yield variant
def get_parsed_functions(): def get_parsed_functions():
def parse_function_parameters(parameters): def parse_function_parameters(parameters):
...@@ -699,20 +692,28 @@ variable_name_count = {} ...@@ -699,20 +692,28 @@ variable_name_count = {}
id_counter = 0 id_counter = 0
def mangledNameHash(str, save_test = True): fnvPrime = 16777619
def hash32(str):
fnvOffsetBasis = 0x811c9dc5 fnvOffsetBasis = 0x811c9dc5
fnvPrime = 16777619
hash = fnvOffsetBasis hash = fnvOffsetBasis
for c in str:
hash = hash ^ ord(c)
hash = (hash * fnvPrime) & 0xffffffff
return hash
def mangledNameHash(str, save_test = True):
hash = hash32(str)
index = 0 index = 0
max_six_bit_value = (1 << 6) - 1 max_six_bit_value = (1 << 6) - 1
paren_location = max_six_bit_value paren_location = max_six_bit_value
has_array_or_block_param_bit = 0
for c in str: for c in str:
hash = hash ^ ord(c)
hash = hash * fnvPrime & 0xffffffff
if c == '(': if c == '(':
paren_location = index paren_location = index
elif c == '{' or c == '[':
has_array_or_block_param_bit = 1
index += 1 index += 1
hash = ((hash >> 12) ^ (hash & 0xfff)) | (paren_location << 26) | (index << 20) hash = ((hash >> 13) ^ (hash & 0x1fff)) | (index << 19) | (paren_location << 25) | (has_array_or_block_param_bit << 31)
if save_test: if save_test:
sanity_check = ' ASSERT_EQ(0x{hash}u, ImmutableString("{str}").mangledNameHash());'.format(hash = ('%08x' % hash), str = str) sanity_check = ' ASSERT_EQ(0x{hash}u, ImmutableString("{str}").mangledNameHash());'.format(hash = ('%08x' % hash), str = str)
script_generated_hash_tests.update({sanity_check: None}) script_generated_hash_tests.update({sanity_check: None})
...@@ -757,22 +758,78 @@ def get_function_mangled_name(function_name, parameters): ...@@ -757,22 +758,78 @@ def get_function_mangled_name(function_name, parameters):
mangled_name += param.get_mangled_name() mangled_name += param.get_mangled_name()
return mangled_name return mangled_name
ttype_mangled_name_variants = []
for basic_type in basic_types_enumeration:
primary_sizes = [1]
secondary_sizes = [1]
if basic_type in ['Float', 'Int', 'UInt', 'Bool']:
primary_sizes = [1, 2, 3, 4]
if basic_type == 'Float':
secondary_sizes = [1, 2, 3, 4]
for primary_size in primary_sizes:
for secondary_size in secondary_sizes:
type = TType({'basic': basic_type, 'primarySize': primary_size, 'secondarySize': secondary_size})
ttype_mangled_name_variants.append(type.get_mangled_name())
def gen_parameters_variant_ids(str_len):
# Note that this doesn't generate variants with array parameters or struct / interface block parameters. They are assumed to have been filtered out separately.
if str_len % 2 != 0:
raise Exception('Expecting parameters mangled name length to be divisible by two')
num_variants = pow(len(ttype_mangled_name_variants), str_len / 2)
return xrange(num_variants)
def get_parameters_mangled_name_variant(variant_id, paren_location, total_length):
str_len = total_length - paren_location - 1
if str_len % 2 != 0:
raise Exception('Expecting parameters mangled name length to be divisible by two')
variant = ''
while (len(variant)) < str_len:
parameter_index = len(variant) / 2
parameter_variant_index = variant_id
for i in xrange(parameter_index):
parameter_variant_index = parameter_variant_index / len(ttype_mangled_name_variants)
parameter_variant_index = parameter_variant_index % len(ttype_mangled_name_variants)
variant += ttype_mangled_name_variants[parameter_variant_index]
return variant
# Calculate the mangled name hash of a common prefix string that's been pre-hashed with hash32()
# plus a variant of the parameters. This is faster than constructing the whole string and then
# calculating the hash for that.
num_type_variants = len(ttype_mangled_name_variants)
def get_mangled_name_variant_hash(prefix_hash32, variant_id, paren_location, total_length):
hash = prefix_hash32
parameter_count = (total_length - paren_location) >> 1
parameter_variant_id_base = variant_id
for parameter_index in xrange(parameter_count):
parameter_variant_index = parameter_variant_id_base % num_type_variants
param_str = ttype_mangled_name_variants[parameter_variant_index]
hash = hash ^ ord(param_str[0])
hash = (hash * fnvPrime) & 0xffffffff
hash = hash ^ ord(param_str[1])
hash = (hash * fnvPrime) & 0xffffffff
parameter_variant_id_base = parameter_variant_id_base / num_type_variants
return ((hash >> 13) ^ (hash & 0x1fff)) | (total_length << 19) | (paren_location << 25)
# Sanity check for get_mangled_name_variant_hash:
if get_mangled_name_variant_hash(hash32("atan("), 3, 4, len("atan(0123")) != mangledNameHash("atan(" + get_parameters_mangled_name_variant(3, 4, len("atan(0123"))):
raise Exception("get_mangled_name_variant_hash sanity check failed")
def mangled_name_hash_can_collide_with_different_parameters(function_variant_props): def mangled_name_hash_can_collide_with_different_parameters(function_variant_props):
# We exhaustively search through all possible lists of parameters and see if any other mangled # We exhaustively search through all possible lists of parameters and see if any other mangled
# name has the same hash. # name has the same hash.
mangled_name = function_variant_props['mangled_name'] mangled_name = function_variant_props['mangled_name']
mangled_name_len = len(mangled_name)
hash = mangledNameHash(mangled_name) hash = mangledNameHash(mangled_name)
mangled_name_prefix = function_variant_props['name'] + '(' mangled_name_prefix = function_variant_props['name'] + '('
paren_location = len(mangled_name_prefix) - 1
prefix_hash32 = hash32(mangled_name_prefix)
parameters_mangled_name_len = len(mangled_name) - len(mangled_name_prefix) parameters_mangled_name_len = len(mangled_name) - len(mangled_name_prefix)
parameters_mangled_name = mangled_name[len(mangled_name_prefix):] parameters_mangled_name = mangled_name[len(mangled_name_prefix):]
if (parameters_mangled_name_len > 4): if (parameters_mangled_name_len > 6):
# In this case a struct parameter or an array parameter would fit to the space reserved for # This increases the complexity of searching for hash collisions considerably, so rather than doing it we just conservatively assume that a hash collision may be possible.
# parameter mangled names. This increases the complexity of searching for hash collisions
# considerably, so rather than doing it we just conservatively assume that a hash collision
# may be possible.
return True return True
for variant in gen_parameters_mangled_name_variants(parameters_mangled_name_len): for variant_id in gen_parameters_variant_ids(parameters_mangled_name_len):
if parameters_mangled_name != variant and mangledNameHash(mangled_name_prefix + variant, False) == hash: if get_mangled_name_variant_hash(prefix_hash32, variant_id, paren_location, mangled_name_len) == hash and get_parameters_mangled_name_variant(variant_id, paren_location, mangled_name_len) != parameters_mangled_name:
return True return True
return False return False
...@@ -1239,3 +1296,6 @@ with open('ParseContext_autogen.h', 'wt') as outfile_header: ...@@ -1239,3 +1296,6 @@ with open('ParseContext_autogen.h', 'wt') as outfile_header:
with open('SymbolTable_autogen.h', 'wt') as outfile_h: with open('SymbolTable_autogen.h', 'wt') as outfile_h:
output_h = template_symboltable_h.format(**output_strings) output_h = template_symboltable_h.format(**output_strings)
outfile_h.write(output_h) outfile_h.write(output_h)
with open(hash_filename, 'wt') as hash_file:
hash_file.write(input_hash)
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment