// Copyright (c) 2026 Petr BalvĂ­n (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 default: // Default to 8 bytes for unknown types. return 8 } }