Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
asm: add Instruction Source metadata (#609)
* elf: add simple benchmark * asm: use sparse metadata Replace the struct based metadata with a linked list. This reduces the amount of memory needed when the metadata is sparse, aka when there are more different kinds of metadata. This also allows packages to have their own private metadata, which we'll use to store BTF ext_infos. The most common operation on metadata is Get, since we need to check for the presence of multiple bits of metadata for every single instruction. This is captured by the get_miss benchmark. The second most operation will be repeated calls to Set. This is captured by the add_first and add_last benchmarks. My initial implementation used a map[interface{}]interface{} to store metadata, but it turns out that a linked list is more efficient. When using a map, each additional call to Set has to copy all previously set keys. We can avoid this with the linked list since we can simply prepend a new item. Timo proposed an implementation based on a slice, but this too suffers from having to copy previous KVs. name \ time/op map.txt slice.txt ll.txt Metadata/add_first-4 97.6ns ± 2% 48.4ns ± 0% 25.1ns ± 2% Metadata/add_last-4 162ns ± 1% 104ns ± 1% 27ns ± 1% Metadata/add_existing-4 15.0ns ± 0% 9.0ns ± 0% 7.2ns ± 1% Metadata/get_miss-4 8.10ns ± 0% 3.73ns ± 0% 2.80ns ± 0% name \ alloc/op map.txt slice.txt ll.txt Metadata/add_first-4 336B ± 0% 56B ± 0% 48B ± 0% Metadata/add_last-4 336B ± 0% 216B ± 0% 48B ± 0% Metadata/add_existing-4 0.00B 0.00B 0.00B Metadata/get_miss-4 0.00B 0.00B 0.00B name \ allocs/op map.txt slice.txt ll.txt Metadata/add_first-4 2.00 ± 0% 2.00 ± 0% 1.00 ± 0% Metadata/add_last-4 2.00 ± 0% 3.00 ± 0% 1.00 ± 0% Metadata/add_existing-4 0.00 0.00 0.00 Metadata/get_miss-4 0.00 0.00 0.00 * asm: add Instruction Source metadata This commit adds Source metadata to Instruction, which is data about the origin of a series of Instructions. A Source can be any fmt.Stringer. Instructions.Format will now print the source information in-line with the Instruction on which it is set. This results in instructions being annotated with original source code when loaded from an ELF file, or custom annotations if fmt.Stringers are ssigned using Instruction.WithSource. Co-authored-by: Dylan Reimerink <dylan@serverius.net>
- Loading branch information
Showing
5 changed files
with
253 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package asm | ||
|
||
// Metadata contains metadata about an instruction. | ||
type Metadata struct { | ||
head *metaElement | ||
} | ||
|
||
type metaElement struct { | ||
next *metaElement | ||
key, value interface{} | ||
} | ||
|
||
// Find the element containing key. | ||
// | ||
// Returns nil if there is no such element. | ||
func (m *Metadata) find(key interface{}) *metaElement { | ||
for e := m.head; e != nil; e = e.next { | ||
if e.key == key { | ||
return e | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Remove an element from the linked list. | ||
// | ||
// Copies as many elements of the list as necessary to remove r, but doesn't | ||
// perform a full copy. | ||
func (m *Metadata) remove(r *metaElement) { | ||
current := &m.head | ||
for e := m.head; e != nil; e = e.next { | ||
if e == r { | ||
// We've found the element we want to remove. | ||
*current = e.next | ||
|
||
// No need to copy the tail. | ||
return | ||
} | ||
|
||
// There is another element in front of the one we want to remove. | ||
// We have to copy it to be able to change metaElement.next. | ||
cpy := &metaElement{key: e.key, value: e.value} | ||
*current = cpy | ||
current = &cpy.next | ||
} | ||
} | ||
|
||
// Set a key to a value. | ||
// | ||
// If value is nil, the key is removed. Avoids modifying old metadata by | ||
// copying if necessary. | ||
func (m *Metadata) Set(key, value interface{}) { | ||
if e := m.find(key); e != nil { | ||
if e.value == value { | ||
// Key is present and the value is the same. Nothing to do. | ||
return | ||
} | ||
|
||
// Key is present with a different value. Create a copy of the list | ||
// which doesn't have the element in it. | ||
m.remove(e) | ||
} | ||
|
||
// m.head is now a linked list that doesn't contain key. | ||
if value == nil { | ||
return | ||
} | ||
|
||
m.head = &metaElement{key: key, value: value, next: m.head} | ||
} | ||
|
||
// Get the value of a key. | ||
// | ||
// Returns nil if no value with the given key is present. | ||
func (m *Metadata) Get(key interface{}) interface{} { | ||
if e := m.find(key); e != nil { | ||
return e.value | ||
} | ||
return nil | ||
} |
Oops, something went wrong.