Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9f6167a4d |
+1
-1
@@ -29,7 +29,7 @@ import (
|
|||||||
|
|
||||||
// version is the release version, stamped at build time via
|
// version is the release version, stamped at build time via
|
||||||
// -ldflags "-X main.version=…" (defaulting to the current release).
|
// -ldflags "-X main.version=…" (defaulting to the current release).
|
||||||
var version = "0.19.0"
|
var version = "0.20.0"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# gasm-devkit — developer tooling for Go's Plan 9 assembler (GAsm).
|
# gasm-devkit — developer tooling for Go's Plan 9 assembler (GAsm).
|
||||||
|
|
||||||
version := "0.19.0"
|
version := "0.20.0"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@just --list
|
@just --list
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package verify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Block describes one basic block within a function: a maximal sequence of
|
||||||
|
// instructions with a single entry point (a label or the function start) and
|
||||||
|
// a single exit (a jump, conditional jump or RET).
|
||||||
|
type Block struct {
|
||||||
|
Offset int // byte offset within the function
|
||||||
|
Label string // label name ("" for the entry block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks identifies the basic blocks of a function from its local labels.
|
||||||
|
// Each label is a potential jump target and therefore a block boundary; the
|
||||||
|
// function entry (offset 0) is always a block. The blocks are returned in
|
||||||
|
// ascending offset order.
|
||||||
|
func (k *Kernel) Blocks(name string) ([]Block, error) {
|
||||||
|
idx, ok := k.funcs[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("verify: function %q not found", name)
|
||||||
|
}
|
||||||
|
fl := k.img.Funcs[idx]
|
||||||
|
|
||||||
|
blocks := []Block{{Offset: 0, Label: "(entry)"}}
|
||||||
|
// Build a reverse map: offset → label name.
|
||||||
|
offToLabel := make(map[int]string, len(fl.Labels))
|
||||||
|
for label, off := range fl.Labels {
|
||||||
|
if off > 0 && off < fl.Size {
|
||||||
|
offToLabel[off] = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Collect and sort offsets.
|
||||||
|
offsets := make([]int, 0, len(offToLabel))
|
||||||
|
for off := range offToLabel {
|
||||||
|
offsets = append(offsets, off)
|
||||||
|
}
|
||||||
|
sort.Ints(offsets)
|
||||||
|
for _, off := range offsets {
|
||||||
|
blocks = append(blocks, Block{Offset: off, Label: offToLabel[off]})
|
||||||
|
}
|
||||||
|
return blocks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockCount returns the number of identified basic blocks for the function.
|
||||||
|
func (k *Kernel) BlockCount(name string) (int, error) {
|
||||||
|
blocks, err := k.Blocks(name)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(blocks), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathFingerprint is the observable output of one function execution: the
|
||||||
|
// values written back into the result slots of the argument block. Two
|
||||||
|
// executions that produce the same fingerprint took observationally
|
||||||
|
// equivalent paths (though they may differ internally).
|
||||||
|
type PathFingerprint struct {
|
||||||
|
Results []uint64 // the result words from the arg block
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfilePaths runs the function with each of the given argument blocks and
|
||||||
|
// collects the distinct output fingerprints. This measures path diversity:
|
||||||
|
// how many observationally different execution paths the input corpus
|
||||||
|
// exercises. Combined with Blocks (the static block count), it gives a
|
||||||
|
// lower bound on code coverage.
|
||||||
|
func (k *Kernel) ProfilePaths(name string, argSets [][]byte, resultOffsets []int) ([]PathFingerprint, error) {
|
||||||
|
idx, ok := k.funcs[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("verify: function %q not found", name)
|
||||||
|
}
|
||||||
|
fl := k.img.Funcs[idx]
|
||||||
|
|
||||||
|
seen := map[string]bool{}
|
||||||
|
var paths []PathFingerprint
|
||||||
|
|
||||||
|
for _, args := range argSets {
|
||||||
|
if len(args) < fl.Args {
|
||||||
|
return nil, fmt.Errorf("verify: %s: arg block too small", name)
|
||||||
|
}
|
||||||
|
out, err := k.CallFunc(name, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fp := PathFingerprint{}
|
||||||
|
key := ""
|
||||||
|
for _, off := range resultOffsets {
|
||||||
|
v := GetUint64(out, off)
|
||||||
|
fp.Results = append(fp.Results, v)
|
||||||
|
key += fmt.Sprintf("%016x", v)
|
||||||
|
}
|
||||||
|
if !seen[key] {
|
||||||
|
seen[key] = true
|
||||||
|
paths = append(paths, fp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package verify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBlocks(t *testing.T) {
|
||||||
|
k := loadBasic(t)
|
||||||
|
|
||||||
|
// The "sum" function has labels: sum_done, sum_loop.
|
||||||
|
blocks, err := k.Blocks("sum")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Blocks(sum): %v", err)
|
||||||
|
}
|
||||||
|
if len(blocks) < 3 {
|
||||||
|
t.Errorf("sum: expected at least 3 blocks (entry + 2 labels), got %d", len(blocks))
|
||||||
|
}
|
||||||
|
if blocks[0].Offset != 0 {
|
||||||
|
t.Errorf("first block offset = %d, want 0", blocks[0].Offset)
|
||||||
|
}
|
||||||
|
t.Logf("sum blocks: %v", blocks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockCount(t *testing.T) {
|
||||||
|
k := loadLZ4Kernel(t)
|
||||||
|
|
||||||
|
n, err := k.BlockCount("decodeBlockAVX2")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BlockCount: %v", err)
|
||||||
|
}
|
||||||
|
// The decoder has many labels (dec_loop, dec_malformed, etc.).
|
||||||
|
if n < 10 {
|
||||||
|
t.Errorf("decodeBlockAVX2: expected at least 10 blocks, got %d", n)
|
||||||
|
}
|
||||||
|
t.Logf("decodeBlockAVX2: %d basic blocks", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProfilePaths(t *testing.T) {
|
||||||
|
k := loadLZ4Kernel(t)
|
||||||
|
|
||||||
|
// Build a corpus of varied LZ4 blocks.
|
||||||
|
var argSets [][]byte
|
||||||
|
blocks := []struct {
|
||||||
|
src []byte
|
||||||
|
dstSize int
|
||||||
|
}{
|
||||||
|
{[]byte{0x00}, 16}, // empty
|
||||||
|
{[]byte{0x50, 'H', 'e', 'l', 'l', 'o'}, 16}, // literals only
|
||||||
|
{[]byte{0x54, 'A', 'A', 'A', 'A', 'A', 5, 0, 0x30, 'B', 'B', 'B'}, 32}, // match
|
||||||
|
{[]byte{0x14, 'X', 1, 0, 0x10, 'Y'}, 16}, // overlapping
|
||||||
|
{[]byte{0x50, 'H'}, 16}, // malformed
|
||||||
|
{[]byte{0x14, 'X', 0, 0}, 16}, // zero offset
|
||||||
|
}
|
||||||
|
for _, b := range blocks {
|
||||||
|
args := make([]byte, 64)
|
||||||
|
if len(b.src) > 0 {
|
||||||
|
PutPtr(args, 0, unsafe.Pointer(&b.src[0]))
|
||||||
|
}
|
||||||
|
PutUint64(args, 8, uint64(len(b.src)))
|
||||||
|
PutUint64(args, 16, uint64(cap(b.src)))
|
||||||
|
dst := make([]byte, b.dstSize)
|
||||||
|
if len(dst) > 0 {
|
||||||
|
PutPtr(args, 24, unsafe.Pointer(&dst[0]))
|
||||||
|
}
|
||||||
|
PutUint64(args, 32, uint64(len(dst)))
|
||||||
|
PutUint64(args, 40, uint64(cap(dst)))
|
||||||
|
argSets = append(argSets, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result offsets: n+48 and code+56.
|
||||||
|
paths, err := k.ProfilePaths("decodeBlockAVX2", argSets, []int{48, 56})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ProfilePaths: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect at least 3 distinct paths: success (various n), malformed, zero offset.
|
||||||
|
if len(paths) < 3 {
|
||||||
|
t.Errorf("expected at least 3 distinct paths, got %d", len(paths))
|
||||||
|
}
|
||||||
|
t.Logf("decodeBlockAVX2: %d distinct output paths from %d inputs", len(paths), len(argSets))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user