yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Ellie HermaszewskaStable names and backwards compat for serialized IR modules (#7644)00746bf09

master
7.0 KiB280 linesraw
1-- Helper function to flatten the instruction hierarchy
2local function flatten_instructions(insts, prefix, result)
3	prefix = prefix or ""
4	result = result or {}
5
6	for _, entry in ipairs(insts) do
7		for name, data in pairs(entry) do
8			local full_name = prefix == "" and name or (prefix .. "." .. name)
9
10			-- If it's a table with numeric indices, it has children
11			if type(data) == "table" and #data > 0 then
12				flatten_instructions(data, full_name, result)
13			else
14				-- Add the current instruction
15				table.insert(result, full_name)
16			end
17		end
18	end
19
20	return result
21end
22
23-- Load instruction definitions
24local function load_instructions(filename)
25	local chunk, err = loadfile(filename)
26	if not chunk then
27		error("Failed to load instruction file: " .. filename .. " - " .. (err or "unknown error"))
28	end
29
30	-- Just execute it normally
31	local result = chunk()
32
33	-- If the file sets a global 'insts', use that
34	if result.insts then
35		return result.insts
36	end
37
38	error("Instruction file must return a table with 'insts' entry")
39end
40
41-- Load stable names table
42local function load_stable_names(filename)
43	local file = io.open(filename, "r")
44	if not file then
45		-- File doesn't exist, return empty table
46		return {}
47	end
48	file:close()
49
50	local chunk, err = loadfile(filename)
51	if not chunk then
52		error("Failed to load stable names file: " .. filename .. " - " .. (err or "unknown error"))
53	end
54
55	local result = chunk()
56
57	-- Validate structure
58	if type(result) ~= "table" then
59		error("Stable names file must return a table")
60	end
61
62	for name, id in pairs(result) do
63		if type(name) ~= "string" then
64			error(string.format("Invalid key: expected string, got %s", type(name)))
65		end
66		if type(id) ~= "number" then
67			error(string.format("Invalid value for '%s': expected number, got %s", name, type(id)))
68		end
69	end
70
71	return result
72end
73
74-- Save stable names table
75local function save_stable_names(filename, stable_names)
76	local file, err = io.open(filename, "w")
77	if not file then
78		error("Failed to open file for writing: " .. filename .. " - " .. (err or "unknown error"))
79	end
80
81	file:write("-- This file is machine generated! any entries written below will be preserved,\n")
82	file:write("-- but things like comments or anything outside the schema won't be preserved\n")
83	file:write("return {\n")
84
85	-- Sort by ID for consistent output
86	local sorted_entries = {}
87	for name, id in pairs(stable_names) do
88		table.insert(sorted_entries, { name = name, id = id })
89	end
90	table.sort(sorted_entries, function(a, b)
91		return a.id < b.id
92	end)
93
94	for _, entry in ipairs(sorted_entries) do
95		-- Escape quotes in name
96		local escaped_name = entry.name:gsub('"', '\\"')
97		file:write(string.format('\t["%s"] = %d,\n', escaped_name, entry.id))
98	end
99	file:write("}\n")
100	file:close()
101end
102
103-- Check for unique IDs
104local function check_unique_ids(stable_names)
105	local seen_ids = {}
106	local duplicates = {}
107
108	for name, id in pairs(stable_names) do
109		if seen_ids[id] then
110			if not duplicates[id] then
111				duplicates[id] = { seen_ids[id] }
112			end
113			table.insert(duplicates[id], name)
114		else
115			seen_ids[id] = name
116		end
117	end
118
119	return duplicates
120end
121
122-- Check bijection
123local function check_bijection(inst_names, stable_names)
124	local missing_from_stable = {}
125	local extra_in_stable = {}
126
127	-- Check for instructions missing from stable names
128	for _, name in ipairs(inst_names) do
129		if stable_names[name] == nil then
130			table.insert(missing_from_stable, name)
131		end
132	end
133
134	-- Check for stable names not in instructions
135	local inst_name_set = {}
136	for _, name in ipairs(inst_names) do
137		inst_name_set[name] = true
138	end
139
140	for name, _ in pairs(stable_names) do
141		if not inst_name_set[name] then
142			table.insert(extra_in_stable, name)
143		end
144	end
145
146	return missing_from_stable, extra_in_stable
147end
148
149-- Get next available ID
150local function get_next_id(stable_names)
151	local max_id = -1
152	for _, id in pairs(stable_names) do
153		if id > max_id then
154			max_id = id
155		end
156	end
157	return max_id + 1
158end
159
160-- Print usage
161local function print_usage()
162	print("Usage: lua check_instructions.lua check|update [inst_file] [stable_file]")
163	print("Commands:")
164	print("  check  - Check bijection and uniqueness (default)")
165	print("  update - Add missing instructions to stable names")
166end
167
168-- Main program
169local function main(args)
170	local command = args[1] or "check"
171	local inst_file = args[2] or "source/slang/slang-ir-insts.lua"
172	local stable_file = args[3] or "source/slang/slang-ir-insts-stable-names.lua"
173
174	-- Validate command
175	local valid_commands = { check = true, update = true }
176	if not valid_commands[command] then
177		print("ERROR: Invalid command: " .. command)
178		print_usage()
179		return 1
180	end
181
182	-- Load data with error handling
183	local ok, insts_or_err = pcall(load_instructions, inst_file)
184	if not ok then
185		print("ERROR: " .. insts_or_err)
186		return 1
187	end
188	local insts = insts_or_err
189
190	ok, stable_names = pcall(load_stable_names, stable_file)
191	if not ok then
192		print("ERROR: " .. stable_names)
193		return 1
194	end
195
196	-- Flatten instruction hierarchy
197	local inst_names = flatten_instructions(insts)
198
199	local has_errors = false
200
201	if command == "check" or command == "all" then
202		print("=== Checking stable names ===")
203
204		-- Check unique IDs
205		local duplicate_ids = check_unique_ids(stable_names)
206		if next(duplicate_ids) then
207			has_errors = true
208			print("ERROR: Duplicate IDs found:")
209			for id, names in pairs(duplicate_ids) do
210				print(string.format("  - ID %d used by: %s", id, table.concat(names, ", ")))
211			end
212		else
213			print("✓ All IDs are unique")
214		end
215
216		-- Check bijection
217		local missing, extra = check_bijection(inst_names, stable_names)
218
219		if #missing > 0 then
220			has_errors = true
221			print(string.format("ERROR: %d instructions missing from stable names:", #missing))
222			for _, name in ipairs(missing) do
223				print("  - " .. name)
224			end
225		else
226			print("✓ All instructions have stable names")
227		end
228
229		if #extra > 0 then
230			print(string.format("WARNING: %d extra entries in stable names (not in instructions):", #extra))
231			for _, name in ipairs(extra) do
232				print("  - " .. name)
233			end
234		else
235			print("✓ No extra entries in stable names")
236		end
237
238		if not has_errors and #extra == 0 then
239			print("✓ Is a bijection")
240		end
241	end
242
243	if command == "update" or command == "all" then
244		print("=== Updating stable names ===")
245
246		-- Don't update if there are errors
247		if has_errors then
248			print("ERROR: Cannot update due to errors in existing stable names")
249			return 1
250		end
251
252		local missing, _ = check_bijection(inst_names, stable_names)
253
254		if #missing > 0 then
255			-- Add missing instructions
256			local next_id = get_next_id(stable_names)
257
258			for _, name in ipairs(missing) do
259				stable_names[name] = next_id
260				next_id = next_id + 1
261			end
262
263			-- Save updated file
264			local ok, err = pcall(save_stable_names, stable_file, stable_names)
265			if not ok then
266				print("ERROR: Failed to save: " .. err)
267				return 1
268			end
269
270			print(string.format("Added %d new instructions to %s", #missing, stable_file))
271		else
272			print("No missing instructions to add")
273		end
274	end
275
276	return has_errors and 1 or 0
277end
278
279-- Run the program
280os.exit(main(arg) or 0)