Files

168 lines
4.6 KiB
Go
Raw Permalink Normal View History

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package verify
import (
"strings"
)
// FuncSig is a parsed // func signature from the assembly source.
type FuncSig struct {
Name string
Params []Param
Results []Param
}
// Param is a single function parameter.
type Param struct {
Name string
Typ string // "[]byte", "[]int32", "int", "*[32]uint16", etc.
}
// IsPointer reports whether the parameter type is a pointer or slice
// (i.e., it needs a buffer address in the argument block).
func (p Param) IsPointer() bool {
return strings.HasPrefix(p.Typ, "[]") || strings.HasPrefix(p.Typ, "*")
}
// ParseFuncSig extracts the function signature from a "// func ..." comment.
func ParseFuncSig(comment string) (FuncSig, bool) {
m := funcSigRe.FindStringSubmatch(strings.TrimSpace(comment))
if m == nil {
return FuncSig{}, false
}
sig := FuncSig{Name: m[1],
Params: parseParamsExported(m[2])}
// Results may be "(a int, b int)" or "int" or "(int, error)".
res := strings.TrimSpace(m[3])
res = strings.TrimPrefix(res, "(")
res = strings.TrimSuffix(res, ")")
if res != "" {
sig.Results = parseParamsExported(res)
}
return sig, true
}
// parseParamsExported splits a parameter list like "a []byte, b []int32" or
// "dst, src []byte" into typed parameters. Go syntax allows grouped names
// where the type at the end applies to every name in the group:// "dst, src []byte" means both dst and src are []byte.
func parseParamsExported(s string) []Param {
s = strings.TrimSpace(s)
if s == "" {
return nil
}
fields := strings.Split(s, ",")
// First pass: extract the type from each field (if present).
types := make([]string, len(fields))
for i, field := range fields {
parts := strings.Fields(strings.TrimSpace(field))
if len(parts) >= 2 {
types[i] = parts[len(parts)-1]
}
}
// Propagate types backward: a field without a type inherits the type from
// the next field that has one (e.g. "dst" inherits "[]byte" from "src []byte").
for i := range fields {
if types[i] == "" {
for j := i + 1; j < len(fields); j++ {
if types[j] != "" {
types[i] = types[j]
break
}
}
}
}
// Second pass: build params.
var out []Param
for i, field := range fields {
field = strings.TrimSpace(field)
if field == "" {
continue
}
parts := strings.Fields(field)
typ := types[i]
if typ == "" {
typ = parts[0] // unnamed: the whole field is the type
out = append(out, Param{Typ: typ})
} else {
out = append(out, Param{Name: parts[0], Typ: typ})
}
}
return out
}
// ExtractFuncSig extracts the // func signature for the named function
// from the assembly source.
func ExtractFuncSig(src, funcName string) (FuncSig, bool) {
lines := strings.Split(src, "\n")
for i, line := range lines {
if sig, ok := ParseFuncSig(line); ok && sig.Name == funcName {
// Verify the next non-comment line is the TEXT directive.
for j := i + 1; j < len(lines); j++ {
trimmed := strings.TrimSpace(lines[j])
if trimmed == "" || strings.HasPrefix(trimmed, "//") {
continue
}
if strings.HasPrefix(trimmed, "TEXT") {
return sig, true
}
break
}
}
}
return FuncSig{}, false
}
// ArgLayout computes the ABI0 argument layout for a function signature.
// Returns the offset of each parameter in the argument block.
func ArgLayout(sig FuncSig) []ArgOffset {
var offsets []ArgOffset
off := 0
for _, p := range sig.Params {
size := paramSize(p.Typ)
offsets = append(offsets, ArgOffset{Name: p.Name, Typ: p.Typ, Offset: off, Size: size, IsPtr: p.IsPointer()})
off += size
}
return offsets
}
// ArgOffset describes one parameter's position in the argument block.
type ArgOffset struct {
Name string
Typ string
Offset int
Size int
IsPtr bool
}
// paramSize returns the size in bytes of a parameter type in the ABI0 layout.
func paramSize(typ string) int {
switch {
case strings.HasPrefix(typ, "[]"):
// Slice: pointer + length + capacity = 24 bytes.
return 24
case strings.HasPrefix(typ, "*"):
// Pointer: 8 bytes.
return 8
case typ == "int", typ == "int64", typ == "uint64", typ == "uintptr":
return 8
case typ == "int32", typ == "uint32", typ == "float32":
return 4
case typ == "int16", typ == "uint16":
return 2
case typ == "int8", typ == "uint8", typ == "byte", typ == "bool":
return 1
case typ == "string":
// ABI0 strings are a two-word header: data pointer + length.
return 16
case typ == "complex64":
return 8
case typ == "complex128":
return 16
default:
// Default to 8 bytes for unknown types.
return 8
}
}