Avoid double-lookup for name searches in tables (#6136)
During table generation, generate the by-value table first,
and remember the index at which a particular opcode or operand
enum value is stored. Then use that directly in the name lookup
tables. This avoids a second lookup.
Fixed: crbug.com/415377082
diff --git a/source/table2.cpp b/source/table2.cpp
index b68aead..4559d77 100644
--- a/source/table2.cpp
+++ b/source/table2.cpp
@@ -34,6 +34,13 @@
return IndexRange{first, count};
}
+struct NameIndex {
+ // Location of the null-terminated name in the global string table kStrings.
+ IndexRange name;
+ // Index of this name's entry in the corresponding by-value table.
+ uint32_t index;
+};
+
struct NameValue {
// Location of the null-terminated name in the global string table kStrings.
IndexRange name;
@@ -43,15 +50,17 @@
// The generated include file contains variables:
//
-// std::array<NameValue,...> kOperandNames:
-// Operand names and values, ordered by (operand kind, name)
+// std::array<NameIndex,...> kOperandNames:
+// Operand names and index, ordered by (operand kind, name)
+// The index part is the named entry's index in kOperandsByValue array.
// Aliases are included as their own entries.
//
// std::array<OperandDesc, ...> kOperandsByValue:
// Operand descriptions, ordered by (operand kind, operand enum value).
//
-// std::array<NameValue,...> kInstructionNames:
-// Instruction names and opcode values, ordered by (name, value)
+// std::array<NameIndex,...> kInstructionNames:
+// Instruction names and index, ordered by (name, value)
+// The index part is the named entry's index in kInstructionDesc array.
// Aliases are included as their own entries.
//
// std::array<InstructionDesc, ...> kInstructionDesc
@@ -175,10 +184,10 @@
// The comparison function knows to use 'name' string to compare against
// when the value is kSentinel.
const auto kSentinel = uint32_t(-1);
- const NameValue needle{{}, kSentinel};
- auto less = [&](const NameValue& lhs, const NameValue& rhs) {
- const char* lhs_chars = lhs.value == kSentinel ? name : getChars(lhs.name);
- const char* rhs_chars = rhs.value == kSentinel ? name : getChars(rhs.name);
+ const NameIndex needle{{}, kSentinel};
+ auto less = [&](const NameIndex& lhs, const NameIndex& rhs) {
+ const char* lhs_chars = lhs.index == kSentinel ? name : getChars(lhs.name);
+ const char* rhs_chars = rhs.index == kSentinel ? name : getChars(rhs.name);
return std::strcmp(lhs_chars, rhs_chars) < 0;
};
@@ -186,7 +195,8 @@
kInstructionNames.end(), needle, less);
if (where != kInstructionNames.end() &&
std::strcmp(getChars(where->name), name) == 0) {
- return LookupOpcode(static_cast<spv::Op>(where->value), desc);
+ *desc = &kInstructionDesc[where->index];
+ return SPV_SUCCESS;
}
return SPV_ERROR_INVALID_LOOKUP;
}
@@ -258,27 +268,28 @@
// The comparison function knows to use (name, name_len) as the
// string to compare against when the value is kSentinel.
const auto kSentinel = uint32_t(-1);
- const NameValue needle{{}, kSentinel};
+ const NameIndex needle{{}, kSentinel};
// The strings in the global string table are null-terminated, and the count
// reflects that. So always deduct 1 from its length.
- auto less = [&](const NameValue& lhs, const NameValue& rhs) {
- const char* lhs_chars = lhs.value == kSentinel ? name : getChars(lhs.name);
- const char* rhs_chars = rhs.value == kSentinel ? name : getChars(rhs.name);
+ auto less = [&](const NameIndex& lhs, const NameIndex& rhs) {
+ const char* lhs_chars = lhs.index == kSentinel ? name : getChars(lhs.name);
+ const char* rhs_chars = rhs.index == kSentinel ? name : getChars(rhs.name);
const auto content_cmp = std::strncmp(lhs_chars, rhs_chars, name_len);
if (content_cmp != 0) {
return content_cmp < 0;
}
const auto lhs_len =
- lhs.value == kSentinel ? name_len : lhs.name.count() - 1;
+ lhs.index == kSentinel ? name_len : lhs.name.count() - 1;
const auto rhs_len =
- rhs.value == kSentinel ? name_len : rhs.name.count() - 1;
+ rhs.index == kSentinel ? name_len : rhs.name.count() - 1;
return lhs_len < rhs_len;
};
auto where = std::lower_bound(span.begin(), span.end(), needle, less);
if (where != span.end() && where->name.count() - 1 == name_len &&
std::strncmp(getChars(where->name), name, name_len) == 0) {
- return LookupOperand(type, where->value, desc);
+ *desc = &kOperandsByValue[where->index];
+ return SPV_SUCCESS;
}
return SPV_ERROR_INVALID_LOOKUP;
}
@@ -295,16 +306,17 @@
// The comparison function knows to use 'name' string to compare against
// when the value is kSentinel.
const auto kSentinel = uint32_t(-1);
- const NameValue needle{{}, kSentinel};
- auto less = [&](const NameValue& lhs, const NameValue& rhs) {
- const char* lhs_chars = lhs.value == kSentinel ? name : getChars(lhs.name);
- const char* rhs_chars = rhs.value == kSentinel ? name : getChars(rhs.name);
+ const NameIndex needle{{}, kSentinel};
+ auto less = [&](const NameIndex& lhs, const NameIndex& rhs) {
+ const char* lhs_chars = lhs.index == kSentinel ? name : getChars(lhs.name);
+ const char* rhs_chars = rhs.index == kSentinel ? name : getChars(rhs.name);
return std::strcmp(lhs_chars, rhs_chars) < 0;
};
auto where = std::lower_bound(span.begin(), span.end(), needle, less);
if (where != span.end() && std::strcmp(getChars(where->name), name) == 0) {
- return LookupExtInst(type, where->value, desc);
+ *desc = &kExtInstByValue[where->index];
+ return SPV_SUCCESS;
}
return SPV_ERROR_INVALID_LOOKUP;
}
diff --git a/utils/ggt.py b/utils/ggt.py
index 2c87c91..92b0290 100755
--- a/utils/ggt.py
+++ b/utils/ggt.py
@@ -309,12 +309,13 @@
the index range into kOperandByValue.
This has mappings for both concrete and corresponding optional operand kinds.
- - kOperandNames: a 1-dimensional array of all operand name-value pairs,
- sorted first by operand kinds, then by operand name.
+ - kOperandNames: a 1-dimensional array of all operand NameIndex
+ entries, sorted first by operand kinds, then by operand name.
+ The name part is represented by an index range into the string table.
+ The index part is the index of this name's entry into the by-value array.
This can have more entries than the by-value array, because names
can have string aliases. For example,the MemorySemantics value 0
is named both "Relaxed" and "None".
- Each entry is represented by an index range into the string table.
Only non-optional operand kinds are represented here.
- kOperandNamesRangeByKind: a mapping from operand kind to the index
@@ -324,6 +325,12 @@
self.header_ignore_decls.append(
"""
+struct NameIndex {
+ // Location of the null-terminated name in the global string table.
+ IndexRange name;
+ // Index of this name's entry in in the associated by-value table.
+ uint32_t index;
+};
struct NameValue {
// Location of the null-terminated name in the global string table.
IndexRange name;
@@ -364,61 +371,12 @@
category = operand_kind_json.get('category')
return category in ['ValueEnum', 'BitEnum']
- # Populate kOperandNames
- operand_names: List[Tuple[IndexRange,int]] = []
- name_range_for_kind: Dict[str,IndexRange] = {}
- for operand_kind_json in self.operand_kinds:
- kind_key: str = convert_operand_kind(operand_kind_json)
- if ShouldEmit(operand_kind_json):
- operands = [Operand(o) for o in operand_kind_json['enumerants']]
- tuples: List[Tuple[str,int,str]] = []
- for o in operands:
- tuples.append((o.enumerant, o.value, kind_key))
- for a in o.aliases:
- tuples.append((a, o.value, kind_key))
- tuples = sorted(tuples, key = lambda t: t[0])
- ir_tuples = [(self.context.AddString(t[0]),t[1],t[2]) for t in tuples]
- name_range_for_kind[kind_key] = IndexRange(len(operand_names), len(ir_tuples))
- operand_names.extend(ir_tuples)
- else:
- pass
- operand_name_strings: List[str] = []
- for i in range(0, len(operand_names)):
- ir, value, kind_key = operand_names[i]
- operand_name_strings.append('{{{}, {}}}, // {} {} in {}'.format(
- str(ir),value,i,self.context.GetString(ir),kind_key))
-
- parts: List[str] = []
- parts.append("""// Operand names and values, ordered by (operand kind, name)
-// The fields in order are:
-// name, either the primary name or an alias, indexing into kStrings
-// enum value""")
- parts.append("static const std::array<NameValue, {}> kOperandNames{{{{".format(len(operand_name_strings)))
- parts.extend([' ' + str(x) for x in operand_name_strings])
- parts.append("}};\n")
- self.body_decls.extend(parts)
-
- parts.append("""// Maps an operand kind to possible names for operands of that kind.
-// The result is an IndexRange into kOperandNames, and the names
-// are sorted by name within that span.
-// An optional variant of a kind maps to the details for the corresponding
-// concrete operand kind.""")
- parts = ["IndexRange OperandNameRangeForKind(spv_operand_type_t type) {\n switch(type) {"]
- for kind_key, ir in name_range_for_kind.items():
- parts.append(" case {}: return {};".format(
- kind_key,
- str(name_range_for_kind[kind_key])))
- for kind in self.operand_kinds_needing_optional_variant:
- parts.append(" case {}: return {};".format(
- ctype(kind, '?'),
- str(name_range_for_kind[ctype(kind,'')])))
- parts.append(" default: break;");
- parts.append(" }\n return IR(0,0);\n}\n")
- self.body_decls.extend(parts)
-
# Populate kOperandsByValue
operands_by_value: List[str] = []
operands_by_value_by_kind: Dict[str,IndexRange] = {}
+ # Maps the operand kind and value to the index into kOperandsByValue
+ index_by_kind_and_value: Dict[Tuple(str,int),int] = {}
+ index = 0
for operand_kind_json in self.operand_kinds:
kind_key: str = convert_operand_kind(operand_kind_json)
if ShouldEmit(operand_kind_json):
@@ -437,6 +395,8 @@
convert_max_required_version(o.lastVersion),
]
operand_descs.append('{' + ','.join([str(d) for d in desc]) + '}}, // {}'.format(kind_key))
+ index_by_kind_and_value[(kind_key,o.value)] = index
+ index += 1
operands_by_value_by_kind[kind_key] = IndexRange(len(operands_by_value), len(operand_descs))
operands_by_value.extend(operand_descs)
else:
@@ -477,6 +437,60 @@
parts.append(" }\n return IR(0,0);\n}\n")
self.body_decls.extend(parts)
+ # Populate kOperandNames
+ operand_names: List[Tuple[IndexRange,int]] = []
+ name_range_for_kind: Dict[str,IndexRange] = {}
+ for operand_kind_json in self.operand_kinds:
+ kind_key: str = convert_operand_kind(operand_kind_json)
+ if ShouldEmit(operand_kind_json):
+ operands = [Operand(o) for o in operand_kind_json['enumerants']]
+ tuples: List[Tuple[str,int,str]] = []
+ for o in operands:
+ tuples.append((o.enumerant, o.value, kind_key))
+ for a in o.aliases:
+ tuples.append((a, o.value, kind_key))
+ tuples = sorted(tuples, key = lambda t: t[0])
+ ir_tuples = [(self.context.AddString(t[0]),t[1],t[2]) for t in tuples]
+ name_range_for_kind[kind_key] = IndexRange(len(operand_names), len(ir_tuples))
+ operand_names.extend(ir_tuples)
+ else:
+ pass
+ operand_name_strings: List[str] = []
+ for i in range(0, len(operand_names)):
+ ir, value, kind_key = operand_names[i]
+ index = index_by_kind_and_value[(kind_key,value)]
+ operand_name_strings.append('{{{}, {}}}, // {} {} in {}'.format(
+ str(ir),index,i,self.context.GetString(ir),kind_key))
+
+ parts: List[str] = []
+ parts.append("""// Operand names and index into kOperandsByValue, ordered by (operand kind, name)
+// The fields in order are:
+// name, either the primary name or an alias, indexing into kStrings
+// index into the kOperandsByValue array""")
+ parts.append("static const std::array<NameIndex, {}> kOperandNames{{{{".format(len(operand_name_strings)))
+ parts.extend([' ' + str(x) for x in operand_name_strings])
+ parts.append("}};\n")
+ self.body_decls.extend(parts)
+
+ parts.append("""// Maps an operand kind to possible names for operands of that kind.
+// The result is an IndexRange into kOperandNames, and the names
+// are sorted by name within that span.
+// An optional variant of a kind maps to the details for the corresponding
+// concrete operand kind.""")
+ parts = ["IndexRange OperandNameRangeForKind(spv_operand_type_t type) {\n switch(type) {"]
+ for kind_key, ir in name_range_for_kind.items():
+ parts.append(" case {}: return {};".format(
+ kind_key,
+ str(name_range_for_kind[kind_key])))
+ for kind in self.operand_kinds_needing_optional_variant:
+ parts.append(" case {}: return {};".format(
+ ctype(kind, '?'),
+ str(name_range_for_kind[ctype(kind,'')])))
+ parts.append(" default: break;");
+ parts.append(" }\n return IR(0,0);\n}\n")
+ self.body_decls.extend(parts)
+
+
def ComputeInstructionTables(self, insts) -> None:
"""
Creates declarations for instruction tables.
@@ -516,32 +530,12 @@
};
""")
- # Create the sorted list of opcode strings, without the 'Op' prefix.
- opcode_name_entries: List[str] = []
- name_value_pairs: List[Tuple[str,int]] = []
- for i in insts:
- name_value_pairs.append((i['opname'][2:], i['opcode']))
- for a in i.get('aliases',[]):
- name_value_pairs.append((a[2:], i['opcode']))
- name_value_pairs = sorted(name_value_pairs)
- inst_name_strings: List[str] = []
- for i in range(0, len(name_value_pairs)):
- name, value = name_value_pairs[i]
- ir = self.context.AddString(name)
- inst_name_strings.append('{{{}, {}}}, // {} {}'.format(str(ir),value,i,name))
- parts: List[str] = []
- parts.append("""// Opcode strings (without the 'Op' prefix) and opcode values, ordered by name.
-// The fields in order are:
-// name, either the primary name or an alias, indexing into kStrings
-// opcode value""")
- parts.append("static const std::array<NameValue, {}> kInstructionNames{{{{".format(len(inst_name_strings)))
- parts.extend([' ' + str(x) for x in inst_name_strings])
- parts.append("}};\n")
- self.body_decls.extend(parts)
-
# Create the array of InstructionDesc
lines: List[str] = []
- for inst in insts:
+ # Maps the opcode name (without "Op" prefix) to its index in the table.
+ index_by_opcode: Dict[int,int] = {}
+ # Sort by opcode, so lookup can use binary search
+ for inst in sorted(insts, key = lambda inst: int(inst['opcode'])):
parts: List[str] = []
opname: str = inst['opname']
@@ -575,6 +569,7 @@
'PrintingClass::' + to_safe_identifier(inst.get('class','@exclude'))
])
+ index_by_opcode[int(inst['opcode'])] = len(lines)
lines.append('{{{}}},'.format(', '.join([str(x) for x in parts])))
parts = []
parts.append("""// Instruction descriptions, ordered by opcode.
@@ -594,6 +589,30 @@
parts.append("}};\n");
self.body_decls.extend(parts)
+ # Create kInstructionNames.
+ opcode_name_entries: List[str] = []
+ name_value_pairs: List[Tuple[str,int]] = []
+ for i in insts:
+ name_value_pairs.append((i['opname'][2:], i['opcode']))
+ for a in i.get('aliases',[]):
+ name_value_pairs.append((a[2:], i['opcode']))
+ name_value_pairs = sorted(name_value_pairs)
+ inst_name_strings: List[str] = []
+ for i in range(0, len(name_value_pairs)):
+ name, value = name_value_pairs[i]
+ ir = self.context.AddString(name)
+ index = index_by_opcode[value]
+ inst_name_strings.append('{{{}, {}}}, // {} {}'.format(str(ir),index,i,name))
+ parts: List[str] = []
+ parts.append("""// Opcode strings (without the 'Op' prefix) and opcode values, ordered by name.
+// The fields in order are:
+// name, either the primary name or an alias, indexing into kStrings
+// index into kInstructionDesc""")
+ parts.append("static const std::array<NameIndex, {}> kInstructionNames{{{{".format(len(inst_name_strings)))
+ parts.extend([' ' + str(x) for x in inst_name_strings])
+ parts.append("}};\n")
+ self.body_decls.extend(parts)
+
def ComputeExtendedInstructions(self, extinsts) -> None:
"""
@@ -619,9 +638,11 @@
- ExtInstByValueRangeForKind: a function mapping from extinst enum to
the index range into kExtInstByValue.
- - kExtInstNames: a 1-dimensional array of all extinst name-value pairs,
+ - kExtInstNames: a 1-dimensional array of all extinst name-index pairs,
sorted first by extinst enum, then by operand name.
- Each entry is represented by an index range into the string table.
+ The name part is represented by an index range into the string table.
+ The index part is the index of this name's entry in the kExtInstByValue
+ array.
- kExtInstNamesRangeByKind: a mapping from operand kind to the index
range into kOperandNames.
@@ -631,6 +652,8 @@
# Create kExtInstByValue
by_value: List[List[Any]] = []
by_value_by_kind: Dict[str,IndexRange] = {}
+ index_by_kind_and_opcode: Dict[Tuple[str,int],int] = {}
+ index = 0
for e in extinsts:
insts_in_set = []
for inst in sorted(e.grammar['instructions'], key = lambda inst: inst['opcode']):
@@ -644,6 +667,8 @@
inst_parts = [str(x) for x in inst_parts]
insts_in_set.append(' {{{}}}, // {} in {}'.format(
','.join(inst_parts), inst['opname'], e.name))
+ index_by_kind_and_opcode[(e.enum_name,int(inst['opcode']))] = index
+ index += 1
by_value_by_kind[e.enum_name] = IndexRange(len(by_value), len(insts_in_set))
by_value.extend(insts_in_set)
@@ -682,15 +707,20 @@
insts_by_name = sorted(e.grammar['instructions'], key = lambda i: i['opname'])
insts_in_set = []
for inst in insts_by_name:
+ index = index_by_kind_and_opcode[(e.enum_name,int(inst['opcode']))]
insts_in_set.append(
' {{{}, {}}}, // {} in {}'.format(
str(self.context.AddString(inst['opname'])),
- inst['opcode'],
+ index,
inst['opname'],
e.name))
by_name_by_kind[e.enum_name] = IndexRange(len(by_name), len(insts_in_set))
by_name.extend(insts_in_set)
- parts.append("static const std::array<NameValue, {}> kExtInstNames{{{{".format(len(by_name)))
+ parts.append("""// Extended instruction opcode names sorted by extended instruction kind, then opcode name.
+// The fields in order are:
+// name
+// index into kExtInstByValue""")
+ parts.append("static const std::array<NameIndex, {}> kExtInstNames{{{{".format(len(by_name)))
parts.extend(by_name)
parts.append('}};\n')
self.body_decls.extend(parts)