git: 9ded074e875c - main - Refactor makesyscalls.lua into a library

From: Brooks Davis <brooks_at_FreeBSD.org>
Date: Wed, 30 Oct 2024 21:08:55 UTC
The branch main has been updated by brooks:

URL: https://cgit.FreeBSD.org/src/commit/?id=9ded074e875c29cb92d5f643801990d7bb23cca4

commit 9ded074e875c29cb92d5f643801990d7bb23cca4
Author:     agge3 <sterspark@gmail.com>
AuthorDate: 2024-10-21 21:42:13 +0000
Commit:     Brooks Davis <brooks@FreeBSD.org>
CommitDate: 2024-10-30 21:04:30 +0000

    Refactor makesyscalls.lua into a library
    
    * main.lua replicates the functionality of makesyscalls.lua
    * Individual files are generated by their associated module
      * Modules can be called as standalone scripts to generate a specific
        file
    * Data and procedures are performed by objects instead of procedual code
    * Bitmasks are replaced by declarative types
    * Temporary files are no longer produced, writing is stored in memory
    * Comments provide explanation to functions and semantics
    
    Google Summer of Code 2024 Final Work Product
    
    Co-authored-by: Warner Losh <imp@freebsd.org>
    Co-authored-by: Kyle Evans <kevans@freebsd.org>
    Co-authored-by: Brooks Davis <brooks@freebsd.org>
    Sponsored by:    Google (GSoC 24)
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/1362
    Signed-off-by: agge3 <sterspark@gmail.com>
---
 sys/tools/syscalls/README.md                 |  49 +++
 sys/tools/syscalls/config.lua                | 312 +++++++++++++++++
 sys/tools/syscalls/core/freebsd-syscall.lua  | 147 ++++++++
 sys/tools/syscalls/core/scarg.lua            | 163 +++++++++
 sys/tools/syscalls/core/scret.lua            |  45 +++
 sys/tools/syscalls/core/syscall.lua          | 497 +++++++++++++++++++++++++++
 sys/tools/syscalls/main.lua                  |  64 ++++
 sys/tools/syscalls/scripts/init_sysent.lua   | 193 +++++++++++
 sys/tools/syscalls/scripts/libsys_h.lua      | 111 ++++++
 sys/tools/syscalls/scripts/syscall_h.lua     |  97 ++++++
 sys/tools/syscalls/scripts/syscall_mk.lua    |  90 +++++
 sys/tools/syscalls/scripts/syscalls.lua      | 109 ++++++
 sys/tools/syscalls/scripts/syscalls_map.lua  |  74 ++++
 sys/tools/syscalls/scripts/sysproto_h.lua    | 242 +++++++++++++
 sys/tools/syscalls/scripts/systrace_args.lua | 268 +++++++++++++++
 sys/tools/syscalls/tools/generator.lua       | 113 ++++++
 sys/tools/syscalls/tools/util.lua            | 194 +++++++++++
 17 files changed, 2768 insertions(+)

diff --git a/sys/tools/syscalls/README.md b/sys/tools/syscalls/README.md
new file mode 100644
index 000000000000..7ae6519360ba
--- /dev/null
+++ b/sys/tools/syscalls/README.md
@@ -0,0 +1,49 @@
+# System call creation library
+Parses `syscalls.master` and packages information into objects with methods.
+Modules reproduce the previous file auto-generation of `makesyscalls.lua`.
+
+We generally assume that this script will be run by flua, however we've
+carefully crafted modules for it that mimic interfaces provided by modules
+available in ports.  Currently, this script is compatible with lua from
+ports along with the compatible luafilesystem and lua-posix modules.
+
+## Usage
+`main.lua` generates all files.
+Files are associated with their respective modules, and modules can be run as
+standalone scripts to generate specific files.
+
+### Examples
+**All files:**
+`# /usr/libexec/flua /usr/src/sys/tools/syscalls/main.lua /usr/src/sys/kern/syscalls.master`
+<br>
+**syscalls.h:**
+`# /usr/libexec/flua /usr/src/sys/tools/syscalls/scripts/syscalls.h /usr/src/sys/kern/syscalls.master`
+
+## Organization
+* `root`
+  * `main.lua` - Main entry point that calls all scripts.
+  * `config.lua` - Contains the global configuration table and associated
+                   configuration functions.
+
+  * `core` (Core Classes)
+    * `syscall.lua` - Packages each system call entry from `syscalls.master`
+                      into a system call object.
+    * `scarg.lua` - Packages each argument for the system call into an argument
+                    object.
+    * `scret.lua` - An object for the return value of the system call.
+    * `freebsd-syscall.lua` - Contains the master system call table after
+                              processing.
+
+  * `scripts`
+    * `init_sysent.lua` - Generates `init_sysent.c`.
+    * `libsys_h.lua` - Generates `lib/libsys/_libsys.h`.
+    * `syscall_h.lua` - Generates `syscall.h`.
+    * `syscall_mk.lua` - Generates `syscall.mk`.
+    * `syscalls.lua` - Generates `syscalls.c`.
+    * `syscalls_map.lua` - Generates `lib/libsys/syscalls.map`.
+    * `sysproto_h.lua` - Generates `sysproto.h`.
+    * `systrace_args.lua` - Generates `systrace_args.c`.
+
+  * `tools`
+    * `util.lua` - Contains utility functions.
+    * `generator.lua` - Handles file generation for the library.
diff --git a/sys/tools/syscalls/config.lua b/sys/tools/syscalls/config.lua
new file mode 100644
index 000000000000..92098a709854
--- /dev/null
+++ b/sys/tools/syscalls/config.lua
@@ -0,0 +1,312 @@
+--
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- Copyright (c) 2021-2024 SRI International
+-- Copyright (c) 2024 Tyler Baxter <agge@FreeBSD.org>
+-- Copyright (c) 2023 Warner Losh <imp@bsdimp.com>
+-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
+--
+
+--
+-- Code to read in the config file that drives this. Since we inherit from the
+-- FreeBSD makesyscall.sh legacy, all config is done through a config file that
+-- sets a number of variables (as noted below); it used to be a .sh file that
+-- was sourced in. This dodges the need to write a command line parser.
+--
+
+local util = require("tools.util")
+
+--
+-- Global config map.
+-- Default configuration is native. Any of these may get replaced by an
+-- optionally specified configuration file.
+--
+local config = {
+	sysnames = "syscalls.c",
+	syshdr = "../sys/syscall.h",
+	sysmk = "/dev/null",
+	syssw = "init_sysent.c",
+	systrace = "systrace_args.c",
+	sysproto = "../sys/sysproto.h",
+	libsysmap = "/dev/null",
+	libsys_h = "/dev/null",
+	sysproto_h = "_SYS_SYSPROTO_H_",
+	syscallprefix = "SYS_",
+	switchname = "sysent",
+	namesname = "syscallnames",
+	abi_flags = {},
+	abi_func_prefix = "",
+	abi_type_suffix = "",
+	abi_long = "long",
+	abi_u_long = "u_long",
+	abi_semid_t = "semid_t",
+	abi_size_t = "size_t",
+	abi_ptr_array_t = "",
+	abi_headers = "",
+	abi_intptr_t = "intptr_t",
+	ptr_intptr_t_cast = "intptr_t",
+	obsol = {},
+	unimpl = {},
+	capabilities_conf = "capabilities.conf",
+	compat_set = "native",
+	mincompat = 0,
+	capenabled = {},
+	-- System calls that require ABI-specific handling.
+	syscall_abi_change = {},
+	-- System calls that appear to require handling, but don't.
+	syscall_no_abi_change = {},
+	-- Keep track of modifications if there are.
+	modifications = {},
+	-- Stores compat_sets from syscalls.conf; config.mergeCompat()
+	-- instantiates.
+	compat_options = {},
+}
+
+--
+-- For each entry, the ABI flag is the key. One may also optionally provide an
+-- expr, which are contained in an array associated with each key; expr gets
+-- applied to each argument type to indicate whether this argument is subject to
+-- ABI change given the configured flags.
+--
+config.known_abi_flags = {
+	long_size = {
+		"_Contains[a-z_]*_long_",
+		"^long [a-z0-9_]+$",
+		"long [*]",
+		"size_t [*]",
+		-- semid_t is not included because it is only used
+		-- as an argument or written out individually and
+		-- said writes are handled by the ksem framework.
+		-- Technically a sign-extension issue exists for
+		-- arguments, but because semid_t is actually a file
+		-- descriptor negative 32-bit values are invalid
+		-- regardless of sign-extension.
+	},
+	time_t_size = {
+		"_Contains[a-z_]*_timet_",
+	},
+	pointer_args = {
+		-- no expr
+	},
+	pointer_size = {
+		"_Contains[a-z_]*_ptr_",
+		"[*][*]",
+	},
+	pair_64bit = {
+		"^dev_t[ ]*$",
+		"^id_t[ ]*$",
+		"^off_t[ ]*$",
+	},
+}
+
+-- All compat option entries should have five entries:
+--	definition: The preprocessor macro that will be set for this.
+--	compatlevel: The level this compatibility should be included at. This
+--	    generally represents the version of FreeBSD that it is compatible 
+--	    with, but ultimately it's just the level of mincompat in which it's
+--	    included.
+--	flag: The name of the flag in syscalls.master.
+--	prefix: The prefix to use for _args and syscall prototype.  This will be
+--	    used as-is, without "_" or any other character appended.
+--	descr: The description of this compat option in init_sysent.c comments.
+-- The special "stdcompat" entry will cause the other five to be autogenerated.
+local compat_option_sets = {
+	native = {
+		{
+			definition = "COMPAT_43",
+			compatlevel = 3,
+			flag = "COMPAT",
+			prefix = "o",
+			descr = "old",
+		},
+		{ stdcompat = "FREEBSD4" },
+		{ stdcompat = "FREEBSD6" },
+		{ stdcompat = "FREEBSD7" },
+		{ stdcompat = "FREEBSD10" },
+		{ stdcompat = "FREEBSD11" },
+		{ stdcompat = "FREEBSD12" },
+		{ stdcompat = "FREEBSD13" },
+		{ stdcompat = "FREEBSD14" },
+	},
+}
+
+--
+-- config looks like a shell script; in fact, the previous makesyscalls.sh
+-- script actually sourced it in.  It had a pretty common format, so we should
+-- be fine to make various assumptions.
+--
+-- This function processes config to be merged into our global config map with
+-- config.merge(). It aborts if there's malformed lines and returns NIL and a
+-- message if no file was provided.
+--
+function config.process(file)
+	local cfg = {}
+	local comment_line_expr = "^%s*#.*"
+	-- We capture any whitespace padding here so we can easily advance to
+	-- the end of the line as needed to check for any trailing bogus bits.
+	-- Alternatively, we could drop the whitespace and instead try to
+	-- use a pattern to strip out the meaty part of the line, but then we
+	-- would need to sanitize the line for potentially special characters.
+	local line_expr = "^([%w%p]+%s*)=(%s*[`\"]?[^\"`]*[`\"]?)"
+
+	if not file then
+		return nil, "No file given"
+	end
+
+	local fh = assert(io.open(file))
+
+	for nextline in fh:lines() do
+		-- Strip any whole-line comments.
+		nextline = nextline:gsub(comment_line_expr, "")
+		-- Parse it into key, value pairs.
+		local key, value = nextline:match(line_expr)
+		if key ~= nil and value ~= nil then
+			local kvp = key .. "=" .. value
+			key = util.trim(key)
+			value = util.trim(value)
+			local delim = value:sub(1,1)
+			if delim == '"' then
+				local trailing_context
+
+				-- Strip off the key/value part.
+				trailing_context = nextline:sub(kvp:len() + 1)
+				-- Strip off any trailing comment.
+				trailing_context = trailing_context:gsub("#.*$",
+				    "")
+				-- Strip off leading/trailing whitespace.
+				trailing_context = util.trim(trailing_context)
+				if trailing_context ~= "" then
+					print(trailing_context)
+					util.abort(1,
+					    "Malformed line: " .. nextline)
+				end
+
+				value = util.trim(value, delim)
+			else
+				-- Strip off potential comments.
+				value = value:gsub("#.*$", "")
+				-- Strip off any padding whitespace.
+				value = util.trim(value)
+				if value:match("%s") then
+					util.abort(1,
+					    "Malformed config line: " ..
+					    nextline)
+				end
+			end
+			cfg[key] = value
+		elseif not nextline:match("^%s*$") then
+			-- Make sure format violations don't get overlooked
+			-- here, but ignore blank lines.  Comments are already
+			-- stripped above.
+			util.abort(1, "Malformed config line: " .. nextline)
+		end
+	end
+
+	assert(fh:close())
+	return cfg
+end
+
+-- Merges processed configuration file into the global config map (see above),
+-- or returns NIL and a message if no file was provided.
+function config.merge(fh)
+	if not fh then
+		return nil, "No file given"
+	end
+
+	local res = assert(config.process(fh))
+
+	for k, v in pairs(res) do
+		if v ~= config[k] then
+			-- Handling of string lists:
+			if k:find("abi_flags") then
+				-- Match for pipe, that's how abi_flags
+				-- is formatted.
+				config[k] = util.setFromString(v, "[^|]+")
+			elseif k:find("capenabled") or
+			    k:find("syscall_abi_change") or
+			    k:find("syscall_no_abi_change") or
+			    k:find("obsol") or
+			    k:find("unimpl") then
+				-- Match for space, that's how these
+				-- are formatted.
+				config[k] = util.setFromString(v, "[^ ]+")
+			else
+				config[k] = v
+			end
+			-- Construct config modified table as config
+			-- is processed.
+			config.modifications[k] = true
+		else
+			-- config wasn't modified.
+			config.modifications[k] = false
+		end
+	end
+end
+
+-- Returns TRUE if there are ABI changes from native for the provided ABI flag.
+function config.abiChanges(name)
+	if config.known_abi_flags[name] == nil then
+		util.abort(1, "abi_changes: unknown flag: " .. name)
+	end
+	return config.abi_flags[name] ~= nil
+end
+
+-- Instantiates config.compat_options.
+function config.mergeCompat()
+	if config.compat_set ~= "" then
+		if not compat_option_sets[config.compat_set] then
+			util.abort(1, "Undefined compat set: " ..
+			    config.compat_set)
+		end
+
+		config.compat_options = compat_option_sets[config.compat_set]
+	end
+end
+
+-- Parses the provided capabilities.conf. Returns a string (comma separated
+-- list) as its formatted in capabilities.conf, or NIL and a message if no file
+-- was provided.
+local function grabCapenabled(file, open_fail_ok)
+	local capentries = {}
+	local commentExpr = "#.*"
+
+	if file == nil then
+		return nil, "No file given"
+	end
+
+	local fh, msg, errno = io.open(file)
+	if fh == nil then
+		if not open_fail_ok then
+			util.abort(errno, msg)
+		end
+		return nil, msg
+	end
+
+	for nextline in fh:lines() do
+		-- Strip any comments.
+		nextline = nextline:gsub(commentExpr, "")
+		if nextline ~= "" then
+			capentries[nextline] = true
+		end
+	end
+
+	assert(fh:close())
+	return capentries
+end
+
+-- Merge capability (Capsicum) configuration into the global config.
+function config.mergeCapability()
+	-- We ignore errors here if we're relying on the default configuration.
+	if not config.modifications.capenabled then
+		config.capenabled = grabCapenabled(config.capabilities_conf,
+		    config.modifications.capabilities_conf == nil)
+	elseif config.capenabled ~= "" then
+		-- We have a comma separated list from the format of
+		-- capabilities.conf, split it into a set with boolean values
+		-- for each key.
+		config.capenabled = util.setFromString(config.capenabled,
+		    "[^,]+")
+	end
+end
+
+return config
diff --git a/sys/tools/syscalls/core/freebsd-syscall.lua b/sys/tools/syscalls/core/freebsd-syscall.lua
new file mode 100644
index 000000000000..193b1e43563c
--- /dev/null
+++ b/sys/tools/syscalls/core/freebsd-syscall.lua
@@ -0,0 +1,147 @@
+--
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- Copyright (c) 2024 Tyler Baxter <agge@FreeBSD.org>
+-- Copyright (c) 2023 Warner Losh <imp@bsdimp.com>
+-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
+--
+
+local syscall = require("core.syscall")
+local util = require("tools.util")
+
+local FreeBSDSyscall = {}
+
+FreeBSDSyscall.__index = FreeBSDSyscall
+
+-- For each compat option in the provided config table, process them and insert
+-- them into known_flags for class syscall.
+function FreeBSDSyscall:processCompat()
+	for _, v in pairs(self.config.compat_options) do
+		if v.stdcompat ~= nil then
+			local stdcompat = v.stdcompat
+			v.definition = "COMPAT_" .. stdcompat:upper()
+			v.compatlevel = tonumber(stdcompat:match("([0-9]+)$"))
+			v.flag = stdcompat:gsub("FREEBSD", "COMPAT")
+			v.prefix = stdcompat:lower() .. "_"
+			v.descr = stdcompat:lower()
+		end
+
+		-- Add compat option to syscall.known_flags.
+		table.insert(syscall.known_flags, v.flag)
+	end
+end
+
+function FreeBSDSyscall:parseSysfile()
+	local file = self.sysfile
+	local config = self.config
+	local commentExpr = "^%s*;.*"
+
+	if file == nil then
+		return nil, "No file given"
+	end
+
+	self.syscalls = {}
+
+	local fh, msg = io.open(file)
+	if fh == nil then
+		return nil, msg
+	end
+
+	local incs = ""
+	local defs = ""
+	local s
+	for line in fh:lines() do
+		line = line:gsub(commentExpr, "") -- Strip any comments.
+		-- NOTE: Can't use pure pattern matching here because of
+		-- the 's' test and this is shorter than a generic pattern
+		-- matching pattern.
+		if line == nil or line == "" then
+			goto skip	-- Blank line, skip this line.
+		elseif s ~= nil then
+			-- If we have a partial system call object s,
+			-- then feed it one more line.
+			if s:add(line) then
+				-- Append to system call list.
+				for t in s:iter() do
+					if t:validate(t.num - 1) then
+						table.insert(self.syscalls, t)
+					else
+						util.abort(1,
+						    "Skipped system call " ..
+						    "at number " .. t.num)
+					end
+				end
+				s = nil
+			end
+		elseif line:match("^#%s*include") then
+			incs = incs .. line .. "\n"
+		elseif line:match("%%ABI_HEADERS%%") then
+			local h = self.config.abi_headers
+			if h ~= nil and h ~= "" then
+				incs = incs .. h .. "\n"
+			end
+		elseif line:match("^#%s*define") then
+			defs = defs .. line.. "\n"
+		elseif line:match("^#") then
+			util.abort(1, "Unsupported cpp op " .. line)
+		else
+			s = syscall:new()
+			if s:add(line) then
+				-- Append to system call list.
+				for t in s:iter() do
+					if t:validate(t.num - 1) then
+						table.insert(self.syscalls, t)
+					else
+						util.abort(1,
+						    "Skipped system call " ..
+						    "at number " .. t.num)
+					end
+				end
+				s = nil
+			end
+		end
+		::skip::
+	end
+
+	-- Special handling for linux nosys.
+	if config.syscallprefix:find("LINUX") ~= nil then
+		s = nil
+	end
+
+	if s ~= nil then
+		util.abort(1, "Dangling system call at the end")
+	end
+
+	assert(fh:close())
+	self.includes = incs
+	self.defines = defs
+end
+
+function FreeBSDSyscall:findStructs()
+	self.structs = {}
+
+	for _, s in pairs(self.syscalls) do
+		if s:native() and not s.type.NODEF then
+			for _, v in ipairs(s.args) do
+				local name = util.structName(v.type)
+				if name ~= nil then
+					self.structs[name] = name
+				end
+			end
+		end
+	end
+end
+
+function FreeBSDSyscall:new(obj)
+	obj = obj or {}
+	setmetatable(obj, self)
+	self.__index = self
+
+	obj:processCompat()
+	obj:parseSysfile()
+	obj:findStructs()
+
+	return obj
+end
+
+return FreeBSDSyscall
diff --git a/sys/tools/syscalls/core/scarg.lua b/sys/tools/syscalls/core/scarg.lua
new file mode 100644
index 000000000000..7ffbf15b3a80
--- /dev/null
+++ b/sys/tools/syscalls/core/scarg.lua
@@ -0,0 +1,163 @@
+--
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- Copyright (c) 2021-2024 SRI International
+-- Copyright (c) 2024 Tyler Baxter <agge@FreeBSD.org>
+-- Copyright (c) 2023 Warner Losh <imp@bsdimp.com>
+-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
+--
+
+local config = require("config")
+local util = require("tools.util")
+
+local scarg = {}
+
+scarg.__index = scarg
+
+-- Check this argument against config for ABI changes from native. Return TRUE
+-- if there are.
+local function checkAbiChanges(arg)
+	for k, v in pairs(config.known_abi_flags) do
+		if config.abiChanges(k) and v ~= nil then
+			for _, e in pairs(v) do
+				if arg:find(e) then
+					return true
+				end
+			end
+		end
+	end
+	return false
+end
+
+-- Strips the Microsoft(R) SAL annotations from this argument.
+local function stripArgAnnotations(arg)
+	arg = arg:gsub("_Contains_[^ ]*[_)] ?", "")
+	arg = arg:gsub("_In[^ ]*[_)] ?", "")
+	arg = arg:gsub("_Out[^ ]*[_)] ?", "")
+	return util.trim(arg)
+end
+
+-- Preprocessing of this argument.
+function scarg:init(line)
+	-- Trim whitespace and trailing comma. We don't want them here;
+	-- these can mess with our processing of this argument.
+	line = util.trim(line)	-- This provides a clearer abort error.
+	self.scarg = util.trim(line, ',')
+
+	self.arg_abi_change = checkAbiChanges(self.scarg)
+	self.changes_abi = self.arg_abi_change
+	self.scarg = stripArgAnnotations(self.scarg)
+
+	self.name = self.scarg:match("([^* ]+)$")
+	-- Our pattern might produce a Lua pattern sequence; that's a malformed
+	-- declaration.
+	local status, type = pcall(function()
+		return util.trim(self.scarg:gsub(self.name .. "$", ""), nil)
+	end)
+	if not status then
+		util.abort(1, "Malformed argument line: " .. line)
+	end
+	self.type = type
+end
+
+-- Processes this argument.
+-- Flags if there's ABI changes from native, converts this argument to the
+-- target ABI, and handles 64-bit argument pairing.
+-- Returns TRUE if this argument is processed and ready to add.
+-- Returns FALSE if it shouldn't be added (the argument type is void).
+function scarg:process()
+	if self.type ~= "" and self.name ~= "void" then
+		-- util.is64bitType() needs a bare type so check it after
+		-- argname is removed.
+		self.changes_abi = self.changes_abi or
+			(config.abiChanges("pair_64bit") and
+			util.is64bitType(self.type))
+
+		self.type = self.type:gsub("intptr_t", config.abi_intptr_t)
+		self.type = self.type:gsub("semid_t", config.abi_semid_t)
+
+		if util.isPtrType(self.type) then
+			self.type = self.type:gsub("size_t", config.abi_size_t)
+			self.type = self.type:gsub("^long", config.abi_long)
+			self.type = self.type:gsub("^u_long", config.abi_u_long)
+			self.type = self.type:gsub("^const u_long", "const " ..
+			   config.abi_u_long)
+		elseif self.type:find("^long$") then
+			self.type = config.abi_long
+		end
+
+		if util.isPtrArrayType(self.type) and
+		   config.abi_ptr_array_t ~= "" then
+			-- `* const *` -> `**`
+			self.type = self.type:gsub("[*][ ]*const[ ]*[*]", "**")
+			-- e.g., `struct aiocb **` -> `uint32_t *`
+			self.type = self.type:gsub("[^*]*[*]",
+			   config.abi_ptr_array_t .. " ", 1)
+		end
+
+		if self.arg_abi_change then
+			self.type = self.type:gsub("(struct [^ ]*)", "%1" ..
+			    config.abi_type_suffix)
+			self.type = self.type:gsub("(union [^ ]*)", "%1" ..
+			    config.abi_type_suffix)
+		end
+		return true
+	end
+	return false
+end
+
+-- For pairing 64-bit arguments, pad if necessary.
+-- Returns TRUE if this argument was padded.
+local function pad(tbl)
+	if #tbl % 2 == 1 then
+		table.insert(tbl, {
+			type = "int",
+			name = "_pad",
+		})
+		return true
+	end
+	return false
+end
+
+-- To append to a system call's argument table. Appends to the end.
+function scarg:append(tbl)
+	if config.abiChanges("pair_64bit") and util.is64bitType(self.type) then
+		pad(tbl)	-- Needs argument padding.
+		table.insert(tbl, {
+			type = "uint32_t",
+			name = self.name .. "1",
+		})
+		table.insert(tbl, {
+			type = "uint32_t",
+			name = self.name .. "2",
+		})
+	else
+		table.insert(tbl, {
+			type = self.type,
+			name = self.name,
+		})
+	end
+end
+
+-- Returns TRUE if this argument has ABI changes from native.
+-- EXAMPLE: 32-bit argument for freebsd32.
+function scarg:changesAbi()
+	return self.changes_abi
+end
+
+function scarg:new(obj, line)
+	obj = obj or { }
+	setmetatable(obj, self)
+	self.__index = self
+
+	-- ABI changes that we only want in this scope.
+	self.arg_abi_change = false
+	-- ABI changes that we want the system call object to see.
+	self.changes_abi = false
+
+	obj:init(line)
+
+	return obj
+end
+
+return scarg
diff --git a/sys/tools/syscalls/core/scret.lua b/sys/tools/syscalls/core/scret.lua
new file mode 100644
index 000000000000..25522b4c830e
--- /dev/null
+++ b/sys/tools/syscalls/core/scret.lua
@@ -0,0 +1,45 @@
+--
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- Copyright (c) 2024 Tyler Baxter <agge@FreeBSD.org>
+-- Copyright (c) 2023 Warner Losh <imp@bsdimp.com>
+-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
+--
+
+local util = require("tools.util")
+
+local scret = {}
+
+scret.__index = scret
+
+-- Processes this return type.
+function scret:process()
+	local words = util.split(self.scret, "%S+")
+	self.scret = words[1]
+	-- Pointer incoming.
+	if words[2]:sub(1,1) == "*" then
+		self.scret = self.scret .. " "
+	end
+	while words[2]:sub(1,1) == "*" do
+		words[2] = words[2]:sub(2)
+		self.scret = self.scret .. "*"
+	end
+end
+
+-- To add this return type to the system call.
+function scret:add()
+	self:process()
+	return self.scret
+end
+
+function scret:new(obj, line)
+	obj = obj or { }
+	setmetatable(obj, self)
+	self.__index = self
+
+	self.scret = line
+
+	return obj
+end
+
+return scret
diff --git a/sys/tools/syscalls/core/syscall.lua b/sys/tools/syscalls/core/syscall.lua
new file mode 100644
index 000000000000..7e8c562dad8a
--- /dev/null
+++ b/sys/tools/syscalls/core/syscall.lua
@@ -0,0 +1,497 @@
+--
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- Copyright (c) 2024 Tyler Baxter <agge@FreeBSD.org>
+-- Copyright (c) 2023 Warner Losh <imp@bsdimp.com>
+-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
+--
+
+local config = require("config")
+local scarg = require("core.scarg")
+local scret = require("core.scret")
+local util = require("tools.util")
+
+local syscall = {}
+
+syscall.__index = syscall
+
+syscall.known_flags = util.set {
+	"STD",
+	"OBSOL",
+	"RESERVED",
+	"UNIMPL",
+	"NODEF",
+	"NOARGS",
+	"NOPROTO",
+	"NOSTD",
+	"NOTSTATIC",
+	"CAPENABLED",
+	"SYSMUX",
+}
+
+-- Native is an arbitrarily large number to have a constant and not
+-- interfere with compat numbers.
+local native = 1000000
+
+-- Processes and assigns the appropriate thread flag for this system call.
+function syscall:processThr()
+	self.thr = "SY_THR_STATIC"
+	for k, _ in pairs(self.type) do
+		if k == "NOTSTATIC" then
+			self.thr = "SY_THR_ABSENT"
+		end
+	end
+end
+
+-- Processes and assigns the appropriate capability flag for this system call.
+-- "SYF_CAPENABLED" for capability enabled; "0" for NOT capability enabled.
+function syscall:processCap()
+	self.cap = "0"
+	local stripped = util.stripAbiPrefix(self.name, self.prefix)
+	if config.capenabled ~= nil and (config.capenabled[self.name] ~= nil or
+		config.capenabled[stripped] ~= nil) then
+		self.cap = "SYF_CAPENABLED"
+	else
+		for k, _ in pairs(self.type) do
+			if k == "CAPENABLED" then
+				self.cap = "SYF_CAPENABLED"
+			end
+		end
+	end
+end
+
+-- Check that this system call has a known type.
+local function checkType(type)
+	for k, _ in pairs(type) do
+		if not syscall.known_flags[k] and not
+			k:match("^COMPAT") then
+			util.abort(1, "Bad type: " .. k)
+		end
+	end
+end
+
+-- If there are ABI changes from native, process this system call to match the
+-- target ABI.
+function syscall:processChangesAbi()
+	-- First, confirm we want to uphold our changes_abi flag.
+	if config.syscall_no_abi_change[self.name] then
+		self.changes_abi = false
+	end
+	self.noproto = not util.isEmpty(config.abi_flags) and
+	    not self.changes_abi
+	if config.abiChanges("pointer_args") then
+		for _, v in ipairs(self.args) do
+			if util.isPtrType(v.type, config.abi_intptr_t) then
+				if config.syscall_no_abi_change[self.name] then
+					print("WARNING: " .. self.name ..
+					    " in syscall_no_abi_change, " ..
+					    "but pointers args are present")
+				end
+				self.changes_abi = true
+				goto ptrfound
+			end
+		end
+		::ptrfound::
+	end
+	if config.syscall_abi_change[self.name] then
+		self.changes_abi = true
+	end
+	if self.changes_abi then
+		self.noproto = false
+	end
+end
+
+-- Final processing of flags. Process any flags that haven't already been
+-- processed (e.g., dictionaries from syscalls.conf).
+function syscall:processFlags()
+	if config.obsol[self.name] or (self:compatLevel() > 0 and
+	    self:compatLevel() < tonumber(config.mincompat)) then
+		self.args = nil
+		self.type.OBSOL = true
+		-- Don't apply any ABI handling, declared as obsolete.
+		self.changes_abi = false
+	end
+	if config.unimpl[self.name] then
+		self.type.UNIMPL = true
+	end
+	if self.noproto or self.type.SYSMUX then
+		self.type.NOPROTO = true
+	end
+	if self.type.NODEF then
+		self.audit = "AUE_NULL"
+	end
+end
+
+-- Returns TRUE if prefix and arg_prefix are assigned; FALSE if they're left
+-- unassigned.  Relies on a valid changes_abi flag, so should be called AFTER
+-- processChangesAbi().
+function syscall:processPrefix()
+	-- If there are ABI changes from native, assign the correct prefixes.
+	if self.changes_abi then
+		self.arg_prefix = config.abi_func_prefix
+		self.prefix = config.abi_func_prefix
+		return true
+	end
+	return false
+end
+
+-- Validate that we're not skipping system calls by comparing this system call
+-- number to the previous system call number.  Called higher up the call stack
+-- by class FreeBSDSyscall.
+function syscall:validate(prev)
+	return prev + 1 == self.num
+end
+
+-- Return the compat prefix for this system call.
+function syscall:compatPrefix()
+	local c = self:compatLevel()
+	if self.type.OBSOL then
+		return "obs_"
+	end
+	if self.type.RESERVED then
+		return "reserved #"
+	end
+	if self.type.UNIMPL then
+		return "unimp_"
+	end
+	if c == 3 then
+		return "o"
+	end
+	if c < native then
+		return "freebsd" .. tostring(c) .. "_"
+	end
+	return ""
+end
+
+-- Return the symbol name for this system call.
+function syscall:symbol()
+	return self:compatPrefix() .. self.name
+end
+
+--
+-- Return the compatibility level for this system call.
+-- 	0 is obsolete.
+-- 	< 0 is this isn't really a system call we care about.
+-- 	3 is 4.3BSD in theory, but anything before FreeBSD 4.
+-- 	>= 4 is FreeBSD version, this system call was replaced with a new
+-- 	    version.
+--
+function syscall:compatLevel()
+	if self.type.UNIMPL or self.type.RESERVED then
+		return -1
+	elseif self.type.OBSOL then
+		return 0
+	elseif self.type.COMPAT then
+		return 3
*** 1933 LINES SKIPPED ***