[go][syscall] Split out abigen parsing into its own package

This change moves the syscalls.abigen parser into its own package. The
parser will continue to be used in syscall/zx/mkfuchsia.go, but also in
a tool to be added to the runtime to generate bindings for resolving
vDSO symbols when not running with cgo.

Change-Id: I95c789d9b7751579b9f070815498acddfbe14f52
diff --git a/src/internal/fuchsia/abigen/defs.go b/src/internal/fuchsia/abigen/defs.go
new file mode 100644
index 0000000..e97b9ac
--- /dev/null
+++ b/src/internal/fuchsia/abigen/defs.go
@@ -0,0 +1,65 @@
+package abigen
+
+// SysArg represents an arg to a vDSO-exported syscall.
+type SysArg struct {
+	Name       string  // Name represents the name of the argument.
+	Type       SysType // Type represents the type of the argument.
+	Constraint string  // Constraint represents constraints on the argument (in/out/inout).
+}
+
+// SysDef represents a definition of a vDSO-exported syscall.
+type SysDef struct {
+	Name     string   // Name is the name of this syscall definition.
+	Ret      SysType  // Ret is the return type of the syscall.
+	Attrs    []string // Attrs contains one or more attributes of the syscall.
+	Args     []SysArg // Args represents the arguments taken by the syscall.
+	VDSOCall bool     // VDSOCall represents whether the syscall is returned directly from the vDSO.
+}
+
+// GoName returns a string representing the Go name of the syscall in the syscall package.
+func (d *SysDef) GoName() string {
+	if d.Exported() {
+		return "Sys_" + d.Name
+	}
+	return "sys_" + d.Name
+}
+
+// Exported determines whether a particular syscall definition should be exported.
+func (d *SysDef) Exported() bool {
+	switch d.Name {
+	case "handle_duplicate",
+		"object_set_profile",
+		"object_wait_many",
+		"channel_create",
+		"channel_read",
+		"channel_read_etc",
+		"channel_write",
+		"pci_get_bar",
+		"pci_map_interrupt",
+		"pci_query_irq_mode",
+		"pci_set_irq_mode",
+		"pci_init",
+		"pci_add_subtract_to_range",
+		"socket_create",
+		"socket_read",
+		"socket_write",
+		"socket_accept",
+		"socket_share",
+		"system_get_dcache_line_size",
+		"system_get_features",
+		"vmar_allocate",
+		"vmar_allocate_old",
+		"vmar_map_old",
+		"vmar_protect_old",
+		"vmar_destroy",
+		"vmo_read",
+		"vmo_write",
+		"vmo_get_size",
+		"vmo_set_size",
+		"vmo_op_range",
+		"vmo_replace_as_executable":
+		return false
+	default:
+		return true
+	}
+}
diff --git a/src/internal/fuchsia/abigen/doc.go b/src/internal/fuchsia/abigen/doc.go
new file mode 100644
index 0000000..18b13fb
--- /dev/null
+++ b/src/internal/fuchsia/abigen/doc.go
@@ -0,0 +1,9 @@
+// The abigen package provides functionality for parsing Fuchsia's syscalls.abigen file,
+// which contains information on syscalls exposed through the vDSO. This file is used to
+// generate stubs and implementation in the syscall package.
+//
+// Example usage:
+//
+//     p := abigen.NewParser(buffer, "/path/to/syscalls.abigen")
+//     syscalls, err := p.Parse()
+package abigen
diff --git a/src/internal/fuchsia/abigen/parser.go b/src/internal/fuchsia/abigen/parser.go
new file mode 100644
index 0000000..a59a38e
--- /dev/null
+++ b/src/internal/fuchsia/abigen/parser.go
@@ -0,0 +1,326 @@
+package abigen
+
+import (
+	"fmt"
+	"strings"
+	"unicode/utf8"
+)
+
+// Parser is a type that is able to parse a syscalls.abigen file.
+type Parser struct {
+	buf       []byte
+	off       int
+	line, col int
+	name      string
+
+	r rune
+}
+
+type token rune
+
+const (
+	tokenEOF token = iota
+	tokenBad
+	tokenIdent
+	tokenNumber
+)
+
+// String implements fmt.Stringer on the token type.
+func (t token) String() string {
+	switch t {
+	case tokenEOF:
+		return "token(EOF)"
+	case tokenBad:
+		return "token(BAD)"
+	case tokenIdent:
+		return "token(Ident)"
+	case tokenNumber:
+		return "token(Number)"
+	default:
+		return "token(" + string(t) + ")"
+	}
+}
+
+func NewParser(buf []byte, fn string) *Parser {
+	return &Parser{
+		buf:  buf,
+		line: 1,
+		name: fn,
+	}
+}
+
+func (p *Parser) Parse() (defs []SysDef, err error) {
+	if len(p.buf) == 0 {
+		return nil, nil
+	}
+
+	p.nextRune()
+	for {
+		t, lit := p.nextToken()
+		if t == tokenEOF {
+			return defs, nil
+		} else if t == tokenBad {
+			return nil, p.errorf("bad token: %s", lit)
+		}
+
+		if lit == "syscall" {
+			def, err := p.parseSyscall()
+			if err != nil {
+				return nil, err
+			}
+
+			// The PCI syscalls are DDK-only and temporary. The hypervisor syscalls
+			// have complex parameter and return types. Exclude all of these.
+			// helpers for them.
+			if strings.Contains(def.Name, "_test") || strings.HasPrefix(def.Name, "pci_") || strings.HasPrefix(def.Name, "guest_") || strings.HasPrefix(def.Name, "vcpu_") {
+				continue
+			}
+
+			defs = append(defs, def)
+		} else {
+			return nil, p.errorf("unexpected token: %q (literal %q)", t, lit)
+		}
+	}
+
+	return defs, nil
+}
+
+func (p *Parser) expect(want token) {
+	t, lit := p.nextToken()
+	if t != want {
+		p.panicf("expected %q but got %q (literal %q)", want, t, lit)
+	}
+}
+
+func (p *Parser) parseAttributes() (attr []string, err error) {
+	for {
+		p.skipWhitespace()
+		if p.r == '(' {
+			return
+		}
+		attr = append(attr, p.nextIdent())
+		p.skipWhitespace()
+		if p.r == ',' {
+			p.nextRune()
+		}
+	}
+}
+
+func (p *Parser) parseArg() (arg SysArg, err error) {
+	if p.r == ')' {
+		return
+	}
+	arg.Name = p.nextIdent()
+	p.expect(':')
+	arg.Type, err = p.parseType()
+	if err != nil {
+		return SysArg{}, err
+	}
+	p.skipWhitespace()
+	if isLetter(p.r) {
+		arg.Constraint = p.nextIdent()
+		p.skipWhitespace()
+	}
+	return
+}
+
+func (p *Parser) parseSyscall() (d SysDef, err error) {
+	d.Name = p.nextIdent()
+	d.Attrs, err = p.parseAttributes()
+	if err != nil {
+		return SysDef{}, err
+	}
+	for _, a := range d.Attrs {
+		if a == "vdsocall" {
+			d.VDSOCall = true
+		}
+	}
+	p.expect('(')
+	for {
+		p.skipWhitespace()
+		if p.r == ')' {
+			p.nextRune()
+			break
+		}
+		arg, err := p.parseArg()
+		if err != nil {
+			return SysDef{}, err
+		}
+		if arg.Name == "" {
+			break
+		}
+		d.Args = append(d.Args, arg)
+		if p.r == ',' {
+			p.nextRune()
+		}
+	}
+	p.skipWhitespace()
+	if p.r != ';' {
+		kw := p.nextIdent()
+		if kw != "returns" {
+			p.panicf("expected \"returns\" but got \"%s\"", kw)
+		}
+		p.expect('(')
+		first := true
+		for {
+			p.skipWhitespace()
+			if p.r == ')' {
+				p.nextRune()
+				break
+			}
+			if first {
+				first = false
+				rtype, err := p.parseType()
+				if err != nil {
+					return SysDef{}, err
+				}
+				d.Ret = rtype
+			} else {
+				arg, err := p.parseArg()
+				if err != nil {
+					return SysDef{}, err
+				}
+				// Extra return types are returned as pointers.
+				arg.Type.Array = true
+				arg.Type.ArraySize = "1"
+				d.Args = append(d.Args, arg)
+			}
+			if p.r == ',' {
+				p.nextRune()
+			}
+		}
+	}
+	p.skipWhitespace()
+	p.expect(';')
+	return d, nil
+}
+
+func (p *Parser) parseType() (t SysType, err error) {
+	ident := p.nextIdent()
+	n, ok := sysTypeNames[ident]
+	if !ok {
+		return t, p.errorf("unknown type name: %s", ident)
+	}
+	t.Name = n
+	p.skipWhitespace()
+	if p.r == '[' {
+		p.nextRune()
+		t.Array = true
+		t.ArraySize = p.nextIdent()
+		p.skipWhitespace()
+		for p.r == '*' {
+			p.nextRune()
+			t.ArraySize += " * " + p.nextIdent()
+		}
+		p.expect(']')
+	}
+	return t, nil
+}
+
+func (p *Parser) nextToken() (t token, lit string) {
+	p.skipWhitespace()
+
+	if isLetter(p.r) {
+		lit = p.nextIdent()
+		return tokenIdent, lit
+	}
+	if isNumber(p.r) {
+		lit = p.nextNumber()
+		return tokenNumber, lit
+	}
+
+	r := p.r
+	p.nextRune()
+	switch r {
+	case -1:
+		return tokenEOF, ""
+	case '(', ')', '*', ',', ';', ':', '[', ']':
+		return token(r), ""
+	case '#':
+		p.skipComment()
+		return p.nextToken()
+	}
+	return tokenBad, fmt.Sprintf("%q", r)
+}
+
+func (p *Parser) nextIdent() string {
+	p.skipWhitespace()
+	if !isLetter(p.r) && !isNumber(p.r) {
+		p.panicf("expected ident but got %q", p.r)
+	}
+	off := p.off - 1
+	for isLetter(p.r) || isNumber(p.r) {
+		p.nextRune()
+	}
+	r := string(p.buf[off : p.off-1])
+	if r == "type" {
+		// "type" is a reserved word in Go.
+		return "typ"
+	}
+	return r
+}
+
+func (p *Parser) nextNumber() string {
+	p.skipWhitespace()
+	if !isNumber(p.r) {
+		p.panicf("expected number but got %q", p.r)
+	}
+	off := p.off - 1
+	for isNumber(p.r) {
+		p.nextRune()
+	}
+	return string(p.buf[off : p.off-1])
+}
+
+func (p *Parser) skipWhitespace() {
+	for p.r == ' ' || p.r == '\t' || p.r == '\n' {
+		p.nextRune()
+	}
+}
+
+func (p *Parser) skipComment() {
+	// skip until end of line
+	p.nextRune()
+	for p.r != '\n' && p.r >= 0 {
+		p.nextRune()
+	}
+}
+
+func (p *Parser) nextRune() {
+	if p.off >= len(p.buf) {
+		p.r = -1
+		return
+	}
+	if p.r == '\n' {
+		p.line++
+		p.col = 0
+	} else {
+		p.col += utf8.RuneLen(p.r)
+	}
+
+	r, w := utf8.DecodeRune(p.buf[p.off:])
+	if r == utf8.RuneError && w == 1 {
+		p.r = -1
+		p.panicf("input is not UTF-8")
+		return
+	}
+	p.r = r
+	p.off += w
+}
+
+func (p *Parser) panicf(format string, args ...interface{}) {
+	panic(parsePanic{err: p.errorf(format, args...)})
+}
+
+func (p *Parser) errorf(format string, args ...interface{}) error {
+	return fmt.Errorf("%s:%d:%d: %s", p.name, p.line, p.col, fmt.Sprintf(format, args...))
+}
+
+func isLetter(r rune) bool { return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' }
+func isNumber(r rune) bool { return '0' <= r && r <= '9' }
+
+type parsePanic struct {
+	err error
+}
+
+func (pp parsePanic) String() string { return pp.err.Error() }
diff --git a/src/internal/fuchsia/abigen/types.go b/src/internal/fuchsia/abigen/types.go
new file mode 100644
index 0000000..9783968
--- /dev/null
+++ b/src/internal/fuchsia/abigen/types.go
@@ -0,0 +1,249 @@
+package abigen
+
+import "fmt"
+
+// SysType represents the type of an argument or vDSO-exported syscall function
+// return value.
+type SysType struct {
+	Name      SysTypeName // Name represents the name of the type.
+	Array     bool        // Array is true when the type represents an array.
+	ArraySize string      // When Array is true, ArraySize represents the size of the array.
+}
+
+// SysTypeName represents the name of a SysType.
+type SysTypeName int
+
+const (
+	zxVoid SysTypeName = iota
+	zxAny
+	zxBool
+	zxUint
+	zxInt
+	zxChar
+	zxUint8
+	zxInt16
+	zxUint16
+	zxInt32
+	zxUint32
+	zxInt64
+	zxUint64
+	zxUintptr
+	zxSize
+	zxStatus
+	zxClock
+	zxTime
+	zxDuration
+	zxKOID
+	zxHandle
+	zxSignals
+	zxSignalsState
+	zxSMCParameters
+	zxSMCResult
+	zxWaitItem
+	zxRights
+	zxVAddr
+	zxPAddr
+	zxProcessInfo
+	zxExceptionStatus
+	zxExceptionBehaviour
+	zxFunctionPtr
+	zxRRec
+	zxChannelCallArgs
+	zxFIFOState
+	zxVMOption
+)
+
+var sysTypeNames = map[string]SysTypeName{
+	"any":                      zxAny,
+	"void":                     zxVoid,
+	"bool":                     zxBool,
+	"unsigned int":             zxUint,
+	"int":                      zxInt,
+	"char":                     zxChar,
+	"uint8_t":                  zxUint8,
+	"int16_t":                  zxInt16,
+	"int32_t":                  zxInt32,
+	"int64_t":                  zxInt64,
+	"uint16_t":                 zxUint16,
+	"uint32_t":                 zxUint32,
+	"uint64_t":                 zxUint64,
+	"uintptr_t":                zxUintptr,
+	"size_t":                   zxSize,
+	"zx_status_t":              zxStatus, // int32_t
+	"zx_clock_t":               zxUint32,
+	"zx_time_t":                zxTime,     // uint64_t monotonic time
+	"zx_duration_t":            zxDuration, // uint64_t nanoseconds
+	"zx_koid_t":                zxKOID,
+	"zx_handle_t":              zxHandle, // int32_t
+	"zx_futex_t":               zxInt,
+	"zx_signals_t":             zxSignals,
+	"zx_signals_state_t":       zxSignalsState,
+	"zx_wait_item_t":           zxWaitItem,
+	"zx_rights_t":              zxRights,
+	"zx_vaddr_t":               zxVAddr,
+	"zx_paddr_t":               zxPAddr,
+	"zx_smc_parameters_t":      zxSMCParameters,
+	"zx_smc_result_t":          zxSMCResult,
+	"zx_process_info_t":        zxProcessInfo,
+	"zx_exception_status_t":    zxExceptionStatus,
+	"zx_exception_behaviour_t": zxExceptionBehaviour,
+	"zx_function_pointer":      zxFunctionPtr,
+	"zx_rrec_t":                zxRRec,
+	"zx_channel_call_args_t":   zxChannelCallArgs,
+	"zx_fifo_state_t":          zxFIFOState,
+	"zx_vm_option_t":           zxVMOption,
+	// TODO(dhobsd): Remove the following temporary types. They could be blacklisted, but this is easier.
+	"zx_cache_policy_t":        zxInt,
+	"zx_port_packet_t":         zxInt,
+	"zx_pci_init_arg_t":        zxInt,
+	"zx_pci_irq_mode_t":        zxInt,
+	"zx_pci_resource_t":        zxInt,
+	"zx_pcie_device_info_t":    zxInt,
+	"zx_pcie_get_nth_info_t":   zxInt,
+	"zx_vcpu_create_args_t":    zxInt,
+	"zx_system_powerctl_arg_t": zxInt,
+	"zx_handle_info_t":         zxInt,
+	"zx_profile_info_t":        zxInt,
+	"zx_pci_bar_t":             zxInt,
+}
+
+// Size returns the size (in bytes) of the type. Its argument represents the size of the register
+// on the machine for which the type is generated.
+func (t SysType) Size() int {
+	if t.Array {
+		return 8
+	}
+	switch t.Name {
+	case zxFunctionPtr:
+		return 8
+	case zxVoid:
+		return 0
+	case zxAny:
+		return 0
+	case zxBool:
+		return 1
+	case zxInt, zxUint, zxUintptr, zxSize:
+		return 8
+	case zxChar, zxUint8:
+		return 1
+	case zxInt16, zxUint16:
+		return 2
+	case zxInt32, zxUint32:
+		return 4
+	case zxInt64, zxUint64:
+		return 8
+	case zxStatus:
+		return 4
+	case zxClock:
+		return 4
+	case zxTime:
+		return 8
+	case zxDuration:
+		return 8
+	case zxKOID:
+		return 8
+	case zxHandle:
+		return 4
+	case zxSignals:
+		return 4
+	case zxRights:
+		return 4
+	case zxVMOption:
+		return 4
+	case zxVAddr, zxPAddr:
+		return 8
+	case zxProcessInfo:
+		panic("cannot pass zxprocess_info")
+	case zxExceptionStatus, zxExceptionBehaviour:
+		return 4
+	default:
+		panic(fmt.Sprintf("UNKNOWN(%s)", t.Name))
+	}
+}
+
+func (t SysType) goType() string {
+	switch t.Name {
+	case zxVoid:
+		return "BAD(void)"
+	case zxAny:
+		return "BAD(any)"
+	case zxBool:
+		return "bool"
+	case zxUint:
+		return "uint"
+	case zxInt:
+		return "int"
+	case zxChar, zxUint8:
+		return "uint8"
+	case zxInt16:
+		return "int16"
+	case zxUint16:
+		return "uint16"
+	case zxInt32:
+		return "int32"
+	case zxUint32:
+		return "uint32"
+	case zxInt64:
+		return "int64"
+	case zxUint64:
+		return "uint64"
+	case zxUintptr:
+		return "uintptr"
+	case zxStatus:
+		return "Status"
+	case zxClock:
+		return "Clock"
+	case zxTime:
+		return "Time"
+	case zxDuration:
+		return "Duration"
+	case zxKOID:
+		return "Koid"
+	case zxHandle:
+		return "Handle"
+	case zxSignals:
+		return "Signals"
+	case zxWaitItem:
+		return "WaitItem"
+	case zxRights:
+		return "Rights"
+	case zxVAddr:
+		return "Vaddr"
+	case zxPAddr:
+		return "Paddr"
+	case zxSize:
+		return "uint"
+	case zxProcessInfo:
+		return "ProcessInfo"
+	case zxExceptionStatus:
+		return "ExceptionStatus"
+	case zxExceptionBehaviour:
+		return "ExceptionBehaviour"
+	case zxRRec:
+		return "Rrec"
+	case zxChannelCallArgs:
+		return "ChannelCallArgs"
+	case zxFIFOState:
+		return "FIFOState"
+	case zxSMCParameters:
+		return "SMCParameters"
+	case zxSMCResult:
+		return "SMCResult"
+	case zxVMOption:
+		return "VMFlag"
+	default:
+		panic(fmt.Sprintf("UNKNOWN(%s)", t.Name))
+	}
+}
+
+// GoType returns the Go type name for the corresponding SysType.
+func (t SysType) GoType() string {
+	if t.Array {
+		if t.Name == zxAny {
+			return "unsafe.Pointer"
+		}
+		return fmt.Sprintf("*%s", t.goType())
+	}
+
+	return t.goType()
+}
diff --git a/src/syscall/zx/mkfuchsia.go b/src/syscall/zx/mkfuchsia.go
index cdd727f..cb8d33b 100644
--- a/src/syscall/zx/mkfuchsia.go
+++ b/src/syscall/zx/mkfuchsia.go
@@ -23,8 +23,8 @@
 	"log"
 	"os"
 	"path/filepath"
-	"strings"
-	"unicode/utf8"
+
+	"internal/fuchsia/abigen"
 )
 
 var (
@@ -56,12 +56,8 @@
 		log.Fatal(err)
 	}
 
-	p := &parser{
-		buf:  b,
-		line: 1,
-		name: syscallsFile,
-	}
-	syscalls, err := p.parse()
+	p := abigen.NewParser(b, syscallsFile)
+	defs, err := p.Parse()
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -69,30 +65,30 @@
 	buf := new(bytes.Buffer)
 	if *stubs {
 		fmt.Fprint(buf, stubsHeader[1:])
-		for _, call := range syscalls {
-			fmt.Fprintf(buf, "//go:cgo_import_dynamic vdso_zx_%s zx_%s\n", call.name, call.name)
+		for _, def := range defs {
+			fmt.Fprintf(buf, "//go:cgo_import_dynamic vdso_zx_%s zx_%s\n", def.Name, def.Name)
 		}
 		fmt.Fprint(buf, "\n")
-		for _, call := range syscalls {
-			fmt.Fprintf(buf, "//go:linkname vdso_zx_%s vdso_zx_%s\n", call.name, call.name)
+		for _, def := range defs {
+			fmt.Fprintf(buf, "//go:linkname vdso_zx_%s vdso_zx_%s\n", def.Name, def.Name)
 		}
 		fmt.Fprint(buf, "\nvar (\n")
-		for _, call := range syscalls {
-			fmt.Fprintf(buf, "\tvdso_zx_%s uintptr\n", call.name)
+		for _, def := range defs {
+			fmt.Fprintf(buf, "\tvdso_zx_%s uintptr\n", def.Name)
 		}
 		fmt.Fprint(buf, ")\n\n")
-		for _, call := range syscalls {
+		for _, def := range defs {
 			fmt.Fprint(buf, "//go:noescape\n")
 			fmt.Fprint(buf, "//go:nosplit\n")
-			printStub(buf, call)
+			printStub(buf, def)
 			fmt.Fprint(buf, "\n")
 		}
 	} else {
 		fmt.Fprint(buf, asmHeader[1:])
-		for _, call := range syscalls {
+		for _, def := range defs {
 			fmt.Fprint(buf, "// ")
-			printStub(buf, call)
-			printAsm(buf, call)
+			printStub(buf, def)
+			printAsm(buf, def)
 			fmt.Fprint(buf, "\n")
 		}
 	}
@@ -125,22 +121,22 @@
 
 `
 
-func printStub(buf *bytes.Buffer, call zirconSyscallDef) {
-	fmt.Fprintf(buf, "func %s(", call.sysname())
-	for i, arg := range call.args {
-		if arg.atype == (zirconType{}) {
+func printStub(buf *bytes.Buffer, def abigen.SysDef) {
+	fmt.Fprintf(buf, "func %s(", def.GoName())
+	for i, arg := range def.Args {
+		if arg.Type == (abigen.SysType{}) {
 			continue
 		}
 		if i > 0 {
 			fmt.Fprint(buf, ", ")
 		}
-		fmt.Fprintf(buf, "%s ", arg.aname)
-		printGoType(buf, arg.atype)
+		fmt.Fprintf(buf, "%s ", arg.Name)
+		fmt.Fprintf(buf, "%s", arg.Type.GoType())
 	}
 	fmt.Fprint(buf, ")")
-	if call.ret != (zirconType{}) {
+	if def.Ret != (abigen.SysType{}) {
 		fmt.Fprint(buf, " ")
-		printGoType(buf, call.ret)
+		fmt.Fprintf(buf, "%s", def.Ret.GoType())
 	}
 	fmt.Fprint(buf, "\n")
 }
@@ -158,13 +154,11 @@
 	"object_wait_many": true,
 }
 
-const ZIRCON_SYSCALL_MAGIC = 0x00ff00ff00000000
-
-func printAsm(buf *bytes.Buffer, call zirconSyscallDef) {
+func printAsm(buf *bytes.Buffer, def abigen.SysDef) {
 	// The summed size of all arguments
 	argSize := 0
-	for _, arg := range call.args {
-		sz := typeSize(arg.atype)
+	for _, arg := range def.Args {
+		sz := arg.Type.Size()
 		if *goarch == "arm64" && sz == 1 {
 			sz = 8
 		}
@@ -178,7 +172,7 @@
 		// Force the return argument on the stack to be 8-byte aligned, not 4-byte aligned
 		argSize += 4
 	}
-	retSize := typeSize(call.ret)
+	retSize := def.Ret.Size()
 
 	var frameSize int
 	var regArgs []string
@@ -190,7 +184,7 @@
 		retReg = "AX"
 		suffix8 = "Q"
 		suffix4 = "L"
-		switch len(call.args) {
+		switch len(def.Args) {
 		case 7:
 			frameSize = 16 + 8
 		case 8:
@@ -206,21 +200,20 @@
 		panic(fmt.Sprintf("goarch=%s not supported", *goarch))
 	}
 
-	fmt.Fprintf(buf, "TEXT ·%s(SB),NOSPLIT,$%d-%d\n", call.sysname(), frameSize, argSize+retSize)
+	fmt.Fprintf(buf, "TEXT ·%s(SB),NOSPLIT,$%d-%d\n", def.GoName(), frameSize, argSize+retSize)
 
-	if _, ok := blockingSyscalls[call.name]; ok {
+	if _, ok := blockingSyscalls[def.Name]; ok {
 		fmt.Fprintf(buf, "\tCALL runtime·entersyscall(SB)\n")
 	}
 	off := 0
-	for i, arg := range call.args {
-		name := arg.aname
+	for i, arg := range def.Args {
+		name := arg.Name
 		suffix := suffix8
-		t := arg.atype
-		if typeSize(t) == 4 {
+		t := arg.Type
+		sz := t.Size()
+		if sz == 4 {
 			suffix = suffix4
-		}
-		sz := typeSize(t)
-		if *goarch == "arm64" && sz == 1 {
+		} else if *goarch == "arm64" && sz == 1 {
 			sz = 8
 		}
 		for off%sz != 0 {
@@ -233,651 +226,35 @@
 	}
 	switch *goarch {
 	case "amd64":
-		if len(call.args) >= 7 {
+		if len(def.Args) >= 7 {
 			fmt.Fprintf(buf, "\tMOVQ SP, BP   // BP is preserved across vsdo call by the x86-64 ABI\n")
 			fmt.Fprintf(buf, "\tANDQ $~15, SP // stack alignment for x86-64 ABI\n")
-			if len(call.args) == 8 {
+			if len(def.Args) == 8 {
 				fmt.Fprintf(buf, "\tPUSHQ R13\n")
 			}
 			fmt.Fprintf(buf, "\tPUSHQ R12\n")
 		}
-		fmt.Fprintf(buf, "\tMOVQ vdso_zx_%s(SB), AX\n", call.name)
+		fmt.Fprintf(buf, "\tMOVQ vdso_zx_%s(SB), AX\n", def.Name)
 		fmt.Fprintf(buf, "\tCALL AX\n")
-		if len(call.args) >= 7 {
+		if len(def.Args) >= 7 {
 			fmt.Fprintf(buf, "\tPOPQ R12\n")
-			if len(call.args) == 8 {
+			if len(def.Args) == 8 {
 				fmt.Fprintf(buf, "\tPOPQ R13\n")
 			}
 			fmt.Fprintf(buf, "\tMOVQ BP, SP\n")
 		}
 	case "arm64":
-		fmt.Fprintf(buf, "\tBL vdso_zx_%s(SB)\n", call.name)
+		fmt.Fprintf(buf, "\tBL vdso_zx_%s(SB)\n", def.Name)
 	}
-	if retSize := typeSize(call.ret); retSize > 0 {
+	if retSize := def.Ret.Size(); retSize > 0 {
 		suffix := suffix8
 		if retSize == 4 {
 			suffix = suffix4
 		}
 		fmt.Fprintf(buf, "\tMOV%s %s, ret+%d(FP)\n", suffix, retReg, argSize)
 	}
-	if _, ok := blockingSyscalls[call.name]; ok {
+	if _, ok := blockingSyscalls[def.Name]; ok {
 		fmt.Fprintf(buf, "\t%s runtime·exitsyscall(SB)\n", callIns)
 	}
 	fmt.Fprintf(buf, "\tRET\n")
 }
-
-func typeSize(t zirconType) int {
-	regSize := 8
-	if *goarch == "arm" {
-		regSize = 4
-	}
-	if t.isArray {
-		return regSize
-	}
-	switch t.named {
-	case zx_function_pointer:
-		return regSize
-	case zx_void:
-		return 0
-	case zx_any:
-		return 0
-	case zx_bool:
-		return 1
-	case zx_int, zx_uint, zx_uintptr_t, zx_size_t:
-		return regSize
-	case zx_char, zx_uint8_t:
-		return 1
-	case zx_int16_t, zx_uint16_t:
-		return 2
-	case zx_int32_t, zx_uint32_t:
-		return 4
-	case zx_int64_t, zx_uint64_t:
-		return 8
-	case zx_status_t:
-		return 4
-	case zx_clock_t:
-		return 4
-	case zx_time_t:
-		return 8
-	case zx_duration_t:
-		return 8
-	case zx_koid_t:
-		return 8
-	case zx_handle_t:
-		return 4
-	case zx_signals_t:
-		return 4
-	case zx_rights_t:
-		return 4
-	case zx_vm_option_t:
-		return 4
-	case zx_vaddr_t, zx_paddr_t:
-		return regSize
-	case zx_process_info_t:
-		panic("cannot pass zx_process_info_t")
-	case zx_exception_status_t, zx_exception_behaviour_t:
-		return 4
-	default:
-		panic(fmt.Sprintf("UNKNOWN(%s)", t.named))
-	}
-}
-
-func printGoType(buf *bytes.Buffer, t zirconType) {
-	if t.isArray {
-		if t.named == zx_any {
-			fmt.Fprint(buf, "unsafe.Pointer")
-			return
-		}
-		fmt.Fprint(buf, "*")
-	}
-	switch t.named {
-	case zx_void:
-		fmt.Fprint(buf, "BAD(void)")
-	case zx_any:
-		fmt.Fprint(buf, "BAD(any)")
-	case zx_bool:
-		fmt.Fprint(buf, "bool")
-	case zx_uint:
-		fmt.Fprint(buf, "uint")
-	case zx_int:
-		fmt.Fprint(buf, "int")
-	case zx_char, zx_uint8_t:
-		fmt.Fprint(buf, "uint8")
-	case zx_int16_t:
-		fmt.Fprint(buf, "int16")
-	case zx_uint16_t:
-		fmt.Fprint(buf, "uint16")
-	case zx_int32_t:
-		fmt.Fprint(buf, "int32")
-	case zx_uint32_t:
-		fmt.Fprint(buf, "uint32")
-	case zx_int64_t:
-		fmt.Fprint(buf, "int64")
-	case zx_uint64_t:
-		fmt.Fprint(buf, "uint64")
-	case zx_uintptr_t:
-		fmt.Fprint(buf, "uintptr")
-	case zx_status_t:
-		fmt.Fprint(buf, "Status")
-	case zx_clock_t:
-		fmt.Fprint(buf, "Clock")
-	case zx_time_t:
-		fmt.Fprint(buf, "Time")
-	case zx_duration_t:
-		fmt.Fprint(buf, "Duration")
-	case zx_koid_t:
-		fmt.Fprint(buf, "Koid")
-	case zx_handle_t:
-		fmt.Fprint(buf, "Handle")
-	case zx_signals_t:
-		fmt.Fprint(buf, "Signals")
-	case zx_wait_item_t:
-		fmt.Fprint(buf, "WaitItem")
-	case zx_rights_t:
-		fmt.Fprint(buf, "Rights")
-	case zx_vaddr_t:
-		fmt.Fprint(buf, "Vaddr")
-	case zx_paddr_t:
-		fmt.Fprint(buf, "Paddr")
-	case zx_size_t:
-		fmt.Fprint(buf, "uint")
-	case zx_process_info_t:
-		fmt.Fprint(buf, "ProcessInfo")
-	case zx_exception_status_t:
-		fmt.Fprint(buf, "ExceptionStatus")
-	case zx_exception_behaviour_t:
-		fmt.Fprint(buf, "ExceptionBehaviour")
-	case zx_rrec_t:
-		fmt.Fprint(buf, "Rrec")
-	case zx_channel_call_args_t:
-		fmt.Fprint(buf, "ChannelCallArgs")
-	case zx_fifo_state_t:
-		fmt.Fprint(buf, "FIFOState")
-	case zx_smc_parameters_t:
-		fmt.Fprint(buf, "SMCParameters")
-	case zx_smc_result_t:
-		fmt.Fprint(buf, "SMCResult")
-	case zx_vm_option_t:
-		fmt.Fprint(buf, "VMFlag")
-	default:
-		panic(fmt.Sprintf("UNKNOWN type: %s", t.named))
-	}
-}
-
-func (p *parser) parse() (syscalls []zirconSyscallDef, err error) {
-	if len(p.buf) == 0 {
-		return syscalls, nil
-	}
-
-	p.nextRune()
-	for {
-		t, lit := p.nextToken()
-		if lit == "syscall" {
-			call, err := p.parseSyscall()
-			if err != nil {
-				return nil, err
-			}
-			if strings.Contains(call.name, "_test") {
-				continue
-			}
-			// The PCI syscalls are DDK-only and temporary, so exclude generation of
-			// helpers for them
-			if strings.HasPrefix(call.name, "pci_") {
-				continue
-			}
-			// The hypervisor syscalls have complex parameter and return types. Skip
-			// generation for these until we need them.
-			if strings.HasPrefix(call.name, "guest_") || strings.HasPrefix(call.name, "vcpu_") {
-				continue
-			}
-			syscalls = append(syscalls, call)
-			continue
-		}
-		if t == tokenEOF {
-			return syscalls, nil
-		}
-		if t == tokenBad {
-			return nil, p.errorf("bad token: %s", lit)
-		}
-		return nil, p.errorf("unexpected token: %q (literal %q)", t, lit)
-	}
-}
-
-func (p *parser) expect(want token) {
-	t, lit := p.nextToken()
-	if t != want {
-		p.panicf("expected %q but got %q (literal %q)", want, t, lit)
-	}
-}
-
-func (p *parser) parseAttributes() (attr []string, err error) {
-	for {
-		p.skipWhitespace()
-		if p.r == '(' {
-			return
-		}
-		attr = append(attr, p.nextIdent())
-		p.skipWhitespace()
-		if p.r == ',' {
-			p.nextRune()
-		}
-	}
-}
-
-func (p *parser) parseArg() (arg zirconArg, err error) {
-	if p.r == ')' {
-		return
-	}
-	arg.aname = p.nextIdent()
-	p.expect(':')
-	atype, err := p.parseType()
-	if err != nil {
-		return
-	}
-	arg.atype = atype
-	p.skipWhitespace()
-	if isLetter(p.r) {
-		arg.constraint = p.nextIdent()
-		p.skipWhitespace()
-	}
-	return
-}
-
-func (p *parser) parseSyscall() (call zirconSyscallDef, err error) {
-	call.name = p.nextIdent()
-	attr, err := p.parseAttributes()
-	if err != nil {
-		return zirconSyscallDef{}, err
-	}
-	for _, a := range attr {
-		if a == "vdsocall" {
-			call.vdsocall = true
-		}
-	}
-	call.attr = attr
-	p.expect('(')
-	for {
-		p.skipWhitespace()
-		if p.r == ')' {
-			p.nextRune()
-			break
-		}
-		arg, err := p.parseArg()
-		if err != nil {
-			return zirconSyscallDef{}, err
-		}
-		if arg.aname == "" {
-			break
-		}
-		call.args = append(call.args, arg)
-		if p.r == ',' {
-			p.nextRune()
-		}
-	}
-	p.skipWhitespace()
-	if p.r != ';' {
-		kw := p.nextIdent()
-		if kw != "returns" {
-			p.panicf("expected \"returns\" but got \"%s\"", kw)
-		}
-		p.expect('(')
-		first := true
-		for {
-			p.skipWhitespace()
-			if p.r == ')' {
-				p.nextRune()
-				break
-			}
-			if first {
-				first = false
-				rtype, err := p.parseType()
-				if err != nil {
-					return zirconSyscallDef{}, err
-				}
-				call.ret = rtype
-			} else {
-				arg, err := p.parseArg()
-				if err != nil {
-					return zirconSyscallDef{}, err
-				}
-				// Extra return types are returned as pointers.
-				arg.atype.isArray = true
-				arg.atype.arraySize = "1"
-				call.args = append(call.args, arg)
-			}
-			if p.r == ',' {
-				p.nextRune()
-			}
-		}
-	}
-	p.skipWhitespace()
-	p.expect(';')
-	return call, nil
-}
-
-func (p *parser) parseType() (t zirconType, err error) {
-	ident := p.nextIdent()
-	n, ok := zirconTypeNamedStr[ident]
-	if !ok {
-		return t, p.errorf("unknown type name: %s", ident)
-	}
-	t.named = n
-	p.skipWhitespace()
-	if p.r == '[' {
-		p.nextRune()
-		t.isArray = true
-		t.arraySize = p.nextIdent()
-		p.skipWhitespace()
-		for p.r == '*' {
-			p.nextRune()
-			t.arraySize += " * " + p.nextIdent()
-		}
-		p.expect(']')
-	}
-	return t, nil
-}
-
-type parser struct {
-	buf       []byte
-	off       int
-	line, col int
-	name      string
-
-	r rune
-}
-
-type token rune
-
-const (
-	tokenEOF    token = -1
-	tokenBad    token = -2
-	tokenIdent  token = -3
-	tokenNumber token = -4
-)
-
-func (t token) String() string {
-	switch t {
-	case tokenEOF:
-		return "token(EOF)"
-	case tokenBad:
-		return "token(BAD)"
-	case tokenIdent:
-		return "token(Ident)"
-	case tokenNumber:
-		return "token(Number)"
-	default:
-		return "token(" + string(t) + ")"
-	}
-}
-
-func (p *parser) nextToken() (t token, lit string) {
-	p.skipWhitespace()
-
-	if isLetter(p.r) {
-		lit = p.nextIdent()
-		return tokenIdent, lit
-	}
-	if isNumber(p.r) {
-		lit = p.nextNumber()
-		return tokenNumber, lit
-	}
-
-	r := p.r
-	p.nextRune()
-	switch r {
-	case -1:
-		return tokenEOF, ""
-	case '(', ')', '*', ',', ';', ':', '[', ']':
-		return token(r), ""
-	case '#':
-		p.skipComment()
-		return p.nextToken()
-	}
-	return tokenBad, fmt.Sprintf("%q", r)
-}
-
-func (p *parser) nextIdent() string {
-	p.skipWhitespace()
-	if !isLetter(p.r) && !isNumber(p.r) {
-		p.panicf("expected ident but got %q", p.r)
-	}
-	off := p.off - 1
-	for isLetter(p.r) || isNumber(p.r) {
-		p.nextRune()
-	}
-	r := string(p.buf[off : p.off-1])
-	if r == "type" {
-		// "type" is a reserved word in Go.
-		return "typ"
-	}
-	return r
-}
-
-func (p *parser) nextNumber() string {
-	p.skipWhitespace()
-	if !isNumber(p.r) {
-		p.panicf("expected number but got %q", p.r)
-	}
-	off := p.off - 1
-	for isNumber(p.r) {
-		p.nextRune()
-	}
-	return string(p.buf[off : p.off-1])
-}
-
-func (p *parser) skipWhitespace() {
-	for p.r == ' ' || p.r == '\t' || p.r == '\n' {
-		p.nextRune()
-	}
-}
-
-func (p *parser) skipComment() {
-	// skip until end of line
-	p.nextRune()
-	for p.r != '\n' && p.r >= 0 {
-		p.nextRune()
-	}
-}
-
-func (p *parser) nextRune() {
-	if p.off >= len(p.buf) {
-		p.r = -1
-		return
-	}
-	if p.r == '\n' {
-		p.line++
-		p.col = 0
-	} else {
-		p.col += utf8.RuneLen(p.r)
-	}
-
-	r, w := utf8.DecodeRune(p.buf[p.off:])
-	if r == utf8.RuneError && w == 1 {
-		p.r = -1
-		p.panicf("input is not UTF-8")
-		return
-	}
-	p.r = r
-	p.off += w
-}
-
-func (p *parser) panicf(format string, args ...interface{}) {
-	panic(parsePanic{err: p.errorf(format, args...)})
-}
-
-func (p *parser) errorf(format string, args ...interface{}) error {
-	return fmt.Errorf("%s:%d:%d: %s", p.name, p.line, p.col, fmt.Sprintf(format, args...))
-}
-
-func isLetter(r rune) bool { return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' }
-func isNumber(r rune) bool { return '0' <= r && r <= '9' }
-
-type parsePanic struct {
-	err error
-}
-
-func (pp parsePanic) String() string { return pp.err.Error() }
-
-type zirconArg struct {
-	aname      string
-	atype      zirconType
-	constraint string
-}
-
-type zirconSyscallDef struct {
-	name     string
-	ret      zirconType
-	attr     []string
-	args     []zirconArg
-	vdsocall bool
-}
-
-func (call zirconSyscallDef) sysname() string {
-	if call.exported() {
-		return "Sys_" + call.name
-	}
-	return "sys_" + call.name
-}
-
-func (call zirconSyscallDef) exported() bool {
-	switch call.name {
-	case "handle_duplicate",
-		"object_set_profile",
-		"object_wait_many",
-		"channel_create",
-		"channel_read",
-		"channel_read_etc",
-		"channel_write",
-		"pci_get_bar",
-		"pci_map_interrupt",
-		"pci_query_irq_mode",
-		"pci_set_irq_mode",
-		"pci_init",
-		"pci_add_subtract_to_range",
-		"socket_create",
-		"socket_read",
-		"socket_write",
-		"socket_accept",
-		"socket_share",
-		"system_get_dcache_line_size",
-		"system_get_features",
-		"vmar_allocate",
-		"vmar_allocate_old",
-		"vmar_map_old",
-		"vmar_protect_old",
-		"vmar_destroy",
-		"vmo_read",
-		"vmo_write",
-		"vmo_get_size",
-		"vmo_set_size",
-		"vmo_op_range",
-		"vmo_replace_as_executable":
-
-		return false
-	default:
-		return true
-	}
-}
-
-type zirconType struct {
-	named     zirconTypeNamed
-	isArray   bool
-	arraySize string
-}
-
-type zirconTypeNamed int
-
-const (
-	zx_void zirconTypeNamed = iota
-	zx_any
-	zx_bool
-	zx_uint
-	zx_int
-	zx_char
-	zx_uint8_t
-	zx_int16_t
-	zx_uint16_t
-	zx_int32_t
-	zx_uint32_t
-	zx_int64_t
-	zx_uint64_t
-	zx_uintptr_t
-	zx_size_t
-	zx_status_t
-	zx_clock_t
-	zx_time_t
-	zx_duration_t
-	zx_koid_t
-	zx_handle_t
-	zx_signals_t
-	zx_signals_state_t
-	zx_smc_parameters_t
-	zx_smc_result_t
-	zx_wait_item_t
-	zx_rights_t
-	zx_vaddr_t
-	zx_paddr_t
-	zx_process_info_t
-	zx_exception_status_t
-	zx_exception_behaviour_t
-	zx_function_pointer
-	zx_rrec_t
-	zx_channel_call_args_t
-	zx_fifo_state_t
-	zx_vm_option_t
-)
-
-var zirconTypeNamedStr = map[string]zirconTypeNamed{
-	"any":                      zx_any,
-	"void":                     zx_void,
-	"bool":                     zx_bool,
-	"unsigned int":             zx_uint,
-	"int":                      zx_int,
-	"char":                     zx_char,
-	"uint8_t":                  zx_uint8_t,
-	"int16_t":                  zx_int16_t,
-	"int32_t":                  zx_int32_t,
-	"int64_t":                  zx_int64_t,
-	"uint16_t":                 zx_uint16_t,
-	"uint32_t":                 zx_uint32_t,
-	"uint64_t":                 zx_uint64_t,
-	"uintptr_t":                zx_uintptr_t,
-	"size_t":                   zx_size_t,
-	"zx_status_t":              zx_status_t, // int32_t
-	"zx_clock_t":               zx_uint32_t,
-	"zx_time_t":                zx_time_t,     // uint64_t monotonic time
-	"zx_duration_t":            zx_duration_t, // uint64_t nanoseconds
-	"zx_koid_t":                zx_koid_t,
-	"zx_handle_t":              zx_handle_t, // int32_t
-	"zx_futex_t":               zx_int,
-	"zx_signals_t":             zx_signals_t,
-	"zx_signals_state_t":       zx_signals_state_t,
-	"zx_wait_item_t":           zx_wait_item_t,
-	"zx_rights_t":              zx_rights_t,
-	"zx_vaddr_t":               zx_vaddr_t,
-	"zx_paddr_t":               zx_paddr_t,
-	"zx_smc_parameters_t":      zx_smc_parameters_t,
-	"zx_smc_result_t":          zx_smc_result_t,
-	"zx_process_info_t":        zx_process_info_t,
-	"zx_exception_status_t":    zx_exception_status_t,
-	"zx_exception_behaviour_t": zx_exception_behaviour_t,
-	"zx_function_pointer":      zx_function_pointer,
-	"zx_rrec_t":                zx_rrec_t,
-	"zx_channel_call_args_t":   zx_channel_call_args_t,
-	"zx_fifo_state_t":          zx_fifo_state_t,
-	"zx_vm_option_t":           zx_vm_option_t,
-	// TODO(smklein): Remove the following temporary types. They could be blacklisted, but this is
-	// easier.
-	"zx_cache_policy_t":        zx_int,
-	"zx_port_packet_t":         zx_int,
-	"zx_pci_init_arg_t":        zx_int,
-	"zx_pci_irq_mode_t":        zx_int,
-	"zx_pci_resource_t":        zx_int,
-	"zx_pcie_device_info_t":    zx_int,
-	"zx_pcie_get_nth_info_t":   zx_int,
-	"zx_vcpu_create_args_t":    zx_int,
-	"zx_system_powerctl_arg_t": zx_int,
-	"zx_handle_info_t":         zx_int,
-	"zx_profile_info_t":        zx_int,
-	"zx_pci_bar_t":             zx_int,
-}