1use crate::{
2 r#type::{
3 AddressBase, AddressDisplacement, AddressDisplacementKind, AddressSign,
4 AddressSizeDirective, ArraySpecifier, AsyncGroupModifier, AtomicOperationModifier,
5 CacheModifier, CallModifier, ConditionModifier, DwarfDirective, EntryFunction,
6 FileDirective, FuncFunction, FunctionAlias, FunctionBody, FunctionDeclarationKind,
7 FunctionDim3, FunctionEntryDirective, FunctionHeaderDirective, FunctionKernelDirective,
8 FunctionLinkage, FunctionStatement, FunctionVisibility, GenericFunctionDeclaration,
9 GlobalAddressSpace, GlobalInitializer, GlobalLinkage, GlobalMutability, GlobalVisibility,
10 InitializerValue, Instruction, LinkingDirective, LinkingDirectiveKind, LocationDirective,
11 MathModeModifier, MemoryOperand, MemoryOrderModifier, MemoryScopeModifier, ModifierKind,
12 Module, ModuleDebugDirective, ModuleDirective, ModuleDirectiveKind,
13 ModuleVariableDirective, NumericLiteral, OpcodeKind, Operand, Parameter,
14 ParameterQualifiers, ParameterSpecifier, ParameterStorage, PointerAddressSpace,
15 PointerQualifier, PragmaDirective, PtxParseError, RegisterDeclaration, RegisterSpecifier,
16 RegisterType, RoundingModifier, ScalarType, SectionDirective, ShuffleModifier,
17 StateSpaceModifier, StatementDirective, StatementSectionDirective, SynchronizationModifier,
18 TargetDirective, TypeModifier, VariableDirective, VariableQualifier, VersionDirective,
19 },
20 InstructionOpcode,
21};
22
23use std::collections::VecDeque;
24
25pub fn parse(source: &str) -> Result<Module, PtxParseError> {
32 Parser::new(source).parse()
33}
34
35pub fn parse_module_directive(
37 line: &str,
38 line_number: usize,
39) -> Result<ModuleDirective, PtxParseError> {
40 let stripped = strip_comments(line);
41 let trimmed = stripped.trim();
42 if trimmed.is_empty() {
43 return Err(PtxParseError::InvalidDirective {
44 line: line_number,
45 message: "empty directive".into(),
46 });
47 }
48 if trimmed.starts_with(".tex") {
49 let tex = parse_tex_directive(trimmed, line_number)?;
50 return Ok(ModuleDirective::ModuleVariable(
51 ModuleVariableDirective::Tex(tex),
52 ));
53 }
54
55 if likely_state_space(trimmed) {
56 if let Some(variable) = parse_state_space_variable(trimmed, line_number)? {
57 return Ok(ModuleDirective::ModuleVariable(variable));
58 }
59 }
60 parse_module_directive_internal(trimmed, line_number)
61}
62
63pub fn parse_entry_directive(line: &str) -> Result<FunctionEntryDirective, PtxParseError> {
64 let mut in_declaration = true;
65 match parse_function_directive_stmt(line.trim(), None, 1, &mut in_declaration)? {
66 FunctionBodyItem::Entry(entry) => Ok(entry),
67 _ => Err(PtxParseError::InvalidDirective {
68 line: 1,
69 message: "expected entry directive".into(),
70 }),
71 }
72}
73
74pub fn parse_stmt_directive(line: &str) -> Result<FunctionStatement, PtxParseError> {
75 let mut in_declaration = false;
76 match parse_function_directive_stmt(line.trim(), None, 1, &mut in_declaration)? {
77 FunctionBodyItem::Statement(stmt) => Ok(stmt),
78 _ => Err(PtxParseError::InvalidDirective {
79 line: 1,
80 message: "expected statement directive".into(),
81 }),
82 }
83}
84
85pub fn parse_instruction_line(line: &str) -> Result<Instruction, PtxParseError> {
86 parse_instruction(line.trim(), None, 1)
87}
88
89struct Parser<'a> {
90 lines: Vec<&'a str>,
91 index: usize,
92}
93
94enum FunctionBodyItem {
95 Entry(FunctionEntryDirective),
96 Statement(FunctionStatement),
97}
98
99enum ParsedFunction {
100 Entry(EntryFunction),
101 Func(FuncFunction),
102 Linking(LinkingDirective),
103}
104
105impl<'a> Parser<'a> {
106 fn new(source: &'a str) -> Self {
107 Self {
108 lines: source.lines().collect(),
109 index: 0,
110 }
111 }
112
113 fn parse(mut self) -> Result<Module, PtxParseError> {
114 let mut module = Module {
115 directives: Vec::new(),
116 };
117
118 while self.index < self.lines.len() {
119 let raw_line = self.lines[self.index];
120 let trimmed = strip_comments(raw_line);
121 self.index += 1;
122
123 if trimmed.is_empty() {
124 continue;
125 }
126
127 if trimmed == "{" || trimmed == "}" {
128 continue;
129 }
130
131 if trimmed.starts_with(".alias") {
132 let alias = parse_alias(&trimmed, self.index)?;
133 module.directives.push(ModuleDirective::FunctionKernel(
134 FunctionKernelDirective::Alias(alias),
135 ));
136 continue;
137 }
138
139 if contains_keyword(&trimmed, ".entry") || contains_keyword(&trimmed, ".func") {
140 match self.parse_function(trimmed)? {
141 ParsedFunction::Entry(function) => {
142 module.directives.push(ModuleDirective::FunctionKernel(
143 FunctionKernelDirective::Entry(function),
144 ));
145 }
146 ParsedFunction::Func(function) => {
147 module.directives.push(ModuleDirective::FunctionKernel(
148 FunctionKernelDirective::Func(function),
149 ));
150 }
151 ParsedFunction::Linking(linking) => {
152 module.directives.push(ModuleDirective::Linking(linking));
153 }
154 }
155 continue;
156 }
157
158 let line_number = self.index;
159 if let Some(state_space) = self.try_parse_state_space(trimmed.clone(), line_number)? {
160 module
161 .directives
162 .push(ModuleDirective::ModuleVariable(state_space));
163 continue;
164 }
165
166 let directive = parse_module_directive_internal(&trimmed, self.index)?;
167 module.directives.push(directive);
168 }
169
170 Ok(module)
171 }
172
173 fn parse_function(&mut self, header_line: String) -> Result<ParsedFunction, PtxParseError> {
174 let header_start_line = self.index; let mut header = header_line;
176 let mut is_declaration = header.contains(';');
177
178 while !header.contains('{') && !is_declaration {
179 let next_line = self
180 .lines
181 .get(self.index)
182 .ok_or(PtxParseError::UnexpectedEof {
183 context: "function header",
184 line: header_start_line,
185 })?;
186 self.index += 1;
187 let trimmed = strip_comments(next_line);
188 if trimmed.is_empty() {
189 continue;
190 }
191 header.push(' ');
192 header.push_str(&trimmed);
193 if trimmed.contains(';') {
194 is_declaration = true;
195 }
196 }
197
198 if is_declaration && !header.contains('{') {
199 if let Some(linking) = parse_function_prototype_linking(&header) {
200 return Ok(ParsedFunction::Linking(linking));
201 }
202
203 panic!(
204 "Unsupported function declaration without body at line {}",
205 header_start_line
206 );
207 }
208
209 let mut header_split = header.splitn(2, '{');
210 let header_part = header_split.next().unwrap_or_default().trim();
211 let after_brace = header_split.next().map(str::trim).unwrap_or("");
212
213 let keyword = if header_part.contains(".entry") {
214 ".entry"
215 } else if header_part.contains(".func") {
216 ".func"
217 } else {
218 return Err(PtxParseError::InvalidFunctionHeader {
219 line: header_start_line,
220 message: "missing .entry or .func keyword".into(),
221 });
222 };
223
224 let (directives, name, params, return_param) =
225 parse_function_header(header_part, keyword, header_start_line)?;
226
227 let mut entry_directives = Vec::new();
228 let mut body_statements = Vec::new();
229 let mut brace_depth: i32 = 1; let mut stmt_buffer = String::new();
231 let mut pending_comment: Option<String> = None;
232 let mut in_declaration = true;
233
234 if !after_brace.is_empty() {
235 if process_body_segment(
236 after_brace,
237 None,
238 header_start_line,
239 &mut brace_depth,
240 &mut stmt_buffer,
241 &mut pending_comment,
242 &mut in_declaration,
243 &mut entry_directives,
244 &mut body_statements,
245 )? {
246 if !stmt_buffer.trim().is_empty() {
247 return Err(PtxParseError::InvalidInstruction {
248 line: header_start_line,
249 message: "unterminated instruction in function body".into(),
250 });
251 }
252
253 let body = FunctionBody {
254 entry_directives,
255 statements: body_statements,
256 };
257 return Ok(match keyword {
258 ".entry" => ParsedFunction::Entry(EntryFunction {
259 name,
260 directives,
261 params,
262 body,
263 }),
264 ".func" => ParsedFunction::Func(FuncFunction {
265 name,
266 directives,
267 return_param,
268 params,
269 body,
270 }),
271 _ => unreachable!("validated keyword"),
272 });
273 }
274 }
275
276 while self.index < self.lines.len() {
277 let raw_line = self.lines[self.index];
278 self.index += 1;
279
280 let trimmed_line = raw_line.trim();
281 if trimmed_line.is_empty() {
282 continue;
283 }
284 if trimmed_line.starts_with("//") || trimmed_line.starts_with('#') {
285 continue;
286 }
287
288 let (content, comment) = split_comment(trimmed_line);
289 if content.trim().is_empty() {
290 if let Some(comment) = comment {
291 pending_comment = Some(comment);
292 }
293 continue;
294 }
295
296 if process_body_segment(
297 &content,
298 comment,
299 self.index,
300 &mut brace_depth,
301 &mut stmt_buffer,
302 &mut pending_comment,
303 &mut in_declaration,
304 &mut entry_directives,
305 &mut body_statements,
306 )? {
307 break;
308 }
309 }
310
311 if brace_depth != 0 {
312 return Err(PtxParseError::UnexpectedEof {
313 context: "function body",
314 line: header_start_line,
315 });
316 }
317
318 if !stmt_buffer.trim().is_empty() {
319 return Err(PtxParseError::InvalidInstruction {
320 line: header_start_line,
321 message: "unterminated instruction in function body".into(),
322 });
323 }
324
325 let body = FunctionBody {
326 entry_directives,
327 statements: body_statements,
328 };
329 Ok(match keyword {
330 ".entry" => ParsedFunction::Entry(EntryFunction {
331 name,
332 directives,
333 params,
334 body,
335 }),
336 ".func" => ParsedFunction::Func(FuncFunction {
337 name,
338 directives,
339 return_param,
340 params,
341 body,
342 }),
343 _ => unreachable!("validated keyword"),
344 })
345 }
346
347 fn try_parse_state_space(
348 &mut self,
349 mut current: String,
350 line_number: usize,
351 ) -> Result<Option<ModuleVariableDirective>, PtxParseError> {
352 if !likely_state_space(¤t) {
353 return Ok(None);
354 }
355
356 let trimmed = current.trim_start();
357 if trimmed.starts_with(".tex") {
358 while !current.trim_end().ends_with(';') {
359 let next_line = self
360 .lines
361 .get(self.index)
362 .ok_or(PtxParseError::UnexpectedEof {
363 context: "texture declaration",
364 line: line_number,
365 })?;
366 self.index += 1;
367 let trimmed = strip_comments(next_line);
368 if trimmed.is_empty() {
369 continue;
370 }
371 if !current
372 .chars()
373 .last()
374 .map(|ch| ch.is_whitespace())
375 .unwrap_or(true)
376 {
377 current.push(' ');
378 }
379 current.push_str(&trimmed);
380 }
381
382 let tex = parse_tex_directive(¤t, line_number)?;
383 return Ok(Some(ModuleVariableDirective::Tex(tex)));
384 }
385
386 while !current.trim_end().ends_with(';') {
387 let next_line = self
388 .lines
389 .get(self.index)
390 .ok_or(PtxParseError::UnexpectedEof {
391 context: "state space declaration",
392 line: line_number,
393 })?;
394 self.index += 1;
395 let trimmed = strip_comments(next_line);
396 if trimmed.is_empty() {
397 continue;
398 }
399 if !current
400 .chars()
401 .last()
402 .map(|ch| ch.is_whitespace())
403 .unwrap_or(true)
404 {
405 current.push(' ');
406 }
407 current.push_str(&trimmed);
408 }
409
410 parse_state_space_variable(¤t, line_number)
411 }
412}
413
414fn process_body_segment(
415 segment: &str,
416 comment: Option<String>,
417 line_number: usize,
418 brace_depth: &mut i32,
419 stmt_buffer: &mut String,
420 pending_comment: &mut Option<String>,
421 in_declaration: &mut bool,
422 entry_directives: &mut Vec<FunctionEntryDirective>,
423 statements: &mut Vec<FunctionStatement>,
424) -> Result<bool, PtxParseError> {
425 let open = count_occurrences(segment, '{') as i32;
426 let close = count_occurrences(segment, '}') as i32;
427 *brace_depth += open;
428 *brace_depth -= close;
429
430 if *brace_depth < 0 {
431 return Err(PtxParseError::InvalidFunctionHeader {
432 line: line_number,
433 message: "mismatched braces in function body".into(),
434 });
435 }
436
437 let sanitized = segment
438 .replace('{', " ")
439 .replace('}', " ")
440 .trim()
441 .to_string();
442
443 if sanitized.is_empty() {
444 if let Some(comment) = comment {
445 *pending_comment = Some(comment);
446 }
447 return Ok(*brace_depth == 0);
448 }
449
450 if sanitized.ends_with(':') || sanitized.starts_with('.') || sanitized.starts_with("@@") {
451 match parse_function_stmt(&sanitized, comment, line_number, in_declaration)? {
452 FunctionBodyItem::Entry(entry) => entry_directives.push(entry),
453 FunctionBodyItem::Statement(stmt) => statements.push(stmt),
454 }
455 stmt_buffer.clear();
456 *pending_comment = None;
457 return Ok(*brace_depth == 0);
458 }
459
460 if !stmt_buffer.is_empty() {
461 stmt_buffer.push(' ');
462 }
463 stmt_buffer.push_str(&sanitized);
464
465 if let Some(comment) = comment {
466 *pending_comment = Some(comment);
467 }
468
469 while let Some(idx) = stmt_buffer.find(';') {
470 let (statement, rest) = stmt_buffer.split_at(idx + 1);
471 let statement = statement.trim();
472 if !statement.is_empty() {
473 match parse_function_stmt(
474 statement,
475 pending_comment.take(),
476 line_number,
477 in_declaration,
478 )? {
479 FunctionBodyItem::Entry(entry) => entry_directives.push(entry),
480 FunctionBodyItem::Statement(stmt) => statements.push(stmt),
481 };
482 } else {
483 pending_comment.take();
484 }
485 *stmt_buffer = rest.trim_start().to_string();
486 }
487
488 Ok(*brace_depth == 0)
489}
490
491fn parse_function_prototype_linking(header: &str) -> Option<LinkingDirective> {
492 let trimmed = header.trim();
493 if trimmed.is_empty() {
494 return None;
495 }
496
497 let mut tokens = trimmed.split_whitespace();
498 let first = tokens.next()?;
499
500 let kind = match first {
501 ".extern" => LinkingDirectiveKind::Extern,
502 ".visible" => LinkingDirectiveKind::Visible,
503 ".weak" => LinkingDirectiveKind::Weak,
504 ".common" => LinkingDirectiveKind::Common,
505 _ => return None,
506 };
507
508 let prototype = trimmed[first.len()..]
509 .trim()
510 .trim_end_matches(';')
511 .trim()
512 .to_string();
513 let raw = trimmed.to_string();
514
515 Some(LinkingDirective {
516 kind,
517 prototype,
518 raw,
519 })
520}
521
522fn parse_module_directive_internal(
523 line: &str,
524 line_number: usize,
525) -> Result<ModuleDirective, PtxParseError> {
526 let trimmed = line.trim();
527
528 if trimmed.starts_with(".version") {
529 let parts: Vec<_> = trimmed.split_whitespace().collect();
530 if parts.len() < 2 {
531 return Err(PtxParseError::InvalidDirective {
532 line: line_number,
533 message: "missing version number".into(),
534 });
535 }
536 let mut version_iter = parts[1].split('.');
537 let major = version_iter
538 .next()
539 .and_then(|s| s.parse::<u32>().ok())
540 .ok_or_else(|| PtxParseError::InvalidDirective {
541 line: line_number,
542 message: "invalid major version".into(),
543 })?;
544 let minor = version_iter
545 .next()
546 .and_then(|s| s.parse::<u32>().ok())
547 .ok_or_else(|| PtxParseError::InvalidDirective {
548 line: line_number,
549 message: "invalid minor version".into(),
550 })?;
551 return Ok(ModuleDirective::Module(ModuleDirectiveKind::Version(
552 VersionDirective { major, minor },
553 )));
554 }
555
556 if trimmed.starts_with(".target") {
557 let rest = trimmed.trim_start_matches(".target").trim();
558 let directive = parse_target_directive(rest, line_number)?;
559 return Ok(ModuleDirective::Module(ModuleDirectiveKind::Target(
560 directive,
561 )));
562 }
563
564 if trimmed.starts_with(".address_size") {
565 let rest = trimmed.trim_start_matches(".address_size").trim();
566 let size = rest
567 .split_whitespace()
568 .next()
569 .and_then(|s| s.parse::<u32>().ok())
570 .ok_or_else(|| PtxParseError::InvalidDirective {
571 line: line_number,
572 message: "invalid address size".into(),
573 })?;
574 return Ok(ModuleDirective::Module(ModuleDirectiveKind::AddressSize(
575 AddressSizeDirective { size },
576 )));
577 }
578
579 if trimmed.starts_with(".file") {
580 let rest = trimmed.trim_start_matches(".file").trim();
581 if rest.is_empty() {
582 return Err(PtxParseError::InvalidDirective {
583 line: line_number,
584 message: "missing index for .file directive".into(),
585 });
586 }
587
588 let index_len = rest.find(char::is_whitespace).unwrap_or(rest.len());
589 let (index_part, remainder) = rest.split_at(index_len);
590 let index = index_part
591 .parse::<u32>()
592 .map_err(|_| PtxParseError::InvalidDirective {
593 line: line_number,
594 message: format!("invalid .file index '{index_part}'"),
595 })?;
596
597 let path_part = remainder.trim();
598 if path_part.is_empty() {
599 return Err(PtxParseError::InvalidDirective {
600 line: line_number,
601 message: "missing path in .file directive".into(),
602 });
603 }
604
605 let path = if let Some(stripped) = path_part.strip_prefix('"') {
606 if let Some(end_idx) = stripped.find('"') {
607 let path = stripped[..end_idx].to_string();
608 if !stripped[end_idx + 1..].trim().is_empty() {
609 return Err(PtxParseError::InvalidDirective {
610 line: line_number,
611 message: "unexpected tokens after .file path".into(),
612 });
613 }
614 path
615 } else {
616 return Err(PtxParseError::InvalidDirective {
617 line: line_number,
618 message: "unterminated quoted path in .file directive".into(),
619 });
620 }
621 } else {
622 path_part.to_string()
623 };
624
625 return Ok(ModuleDirective::Debug(ModuleDebugDirective::File(
626 FileDirective { index, path },
627 )));
628 }
629
630 if trimmed.starts_with(".section") {
631 let rest = trimmed.trim_start_matches(".section").trim();
632 if rest.is_empty() {
633 return Err(PtxParseError::InvalidDirective {
634 line: line_number,
635 message: "missing section name".into(),
636 });
637 }
638
639 let mut parts = rest.split_whitespace();
640 let name = parts
641 .next()
642 .ok_or_else(|| PtxParseError::InvalidDirective {
643 line: line_number,
644 message: "missing section name".into(),
645 })?
646 .to_string();
647 let attributes = parts.map(|part| part.trim().to_string()).collect();
648 return Ok(ModuleDirective::Debug(ModuleDebugDirective::Section(
649 SectionDirective { name, attributes },
650 )));
651 }
652
653 if trimmed.starts_with("@@") {
654 let mut parts = trimmed.split_whitespace();
655 let keyword = parts.next().unwrap_or_default();
656 let arguments = parts.map(|token| token.to_string()).collect();
657 let directive = DwarfDirective {
658 keyword: keyword.to_string(),
659 arguments,
660 comment: None,
661 raw: trimmed.to_string(),
662 };
663 return Ok(ModuleDirective::Debug(ModuleDebugDirective::Dwarf(
664 directive,
665 )));
666 }
667
668 if let Some(linking) = parse_module_linking(trimmed, line_number)? {
669 return Ok(ModuleDirective::Linking(linking));
670 }
671
672 Err(PtxParseError::InvalidDirective {
673 line: line_number,
674 message: format!("unsupported directive '{line}'"),
675 })
676}
677
678fn parse_module_linking(
679 trimmed: &str,
680 line_number: usize,
681) -> Result<Option<LinkingDirective>, PtxParseError> {
682 for (keyword, kind) in [
683 (".extern", LinkingDirectiveKind::Extern),
684 (".visible", LinkingDirectiveKind::Visible),
685 (".weak", LinkingDirectiveKind::Weak),
686 (".common", LinkingDirectiveKind::Common),
687 ] {
688 if trimmed.starts_with(keyword) {
689 let remainder = trimmed[keyword.len()..].trim();
690 if remainder.is_empty() {
691 return Err(PtxParseError::InvalidDirective {
692 line: line_number,
693 message: format!("missing prototype for {keyword} directive"),
694 });
695 }
696 let prototype = remainder.trim_end_matches(';').trim().to_string();
697 return Ok(Some(LinkingDirective {
698 kind,
699 prototype,
700 raw: trimmed.to_string(),
701 }));
702 }
703 }
704
705 Ok(None)
706}
707
708fn parse_tex_directive(line: &str, line_number: usize) -> Result<VariableDirective, PtxParseError> {
709 let trimmed = line.trim();
710 if !trimmed.starts_with(".tex") {
711 return Err(PtxParseError::InvalidDirective {
712 line: line_number,
713 message: "expected .tex directive".into(),
714 });
715 }
716
717 let without_semicolon = trimmed.trim_end_matches(';').trim();
718 let mut tokens: Vec<String> = without_semicolon
719 .split_whitespace()
720 .map(|tok| tok.trim().to_string())
721 .filter(|tok| !tok.is_empty())
722 .collect();
723
724 if tokens.is_empty() || tokens[0] != ".tex" {
725 return Err(PtxParseError::InvalidDirective {
726 line: line_number,
727 message: "malformed .tex directive".into(),
728 });
729 }
730
731 tokens.remove(0);
732 if tokens.is_empty() {
733 return Err(PtxParseError::InvalidDirective {
734 line: line_number,
735 message: "missing identifier in .tex directive".into(),
736 });
737 }
738
739 let name_token = tokens.pop().unwrap();
740 if name_token.starts_with('.') {
741 return Err(PtxParseError::InvalidDirective {
742 line: line_number,
743 message: "invalid texture identifier".into(),
744 });
745 }
746
747 let name = name_token.trim_end_matches(';').to_string();
748 let mut qualifiers = Vec::new();
749 let mut ty = None;
750 for token in tokens {
751 if let Some(scalar) = parse_scalar_type(&token) {
752 if ty.is_some() {
753 return Err(PtxParseError::InvalidDirective {
754 line: line_number,
755 message: "multiple type specifiers in .tex directive".into(),
756 });
757 }
758 ty = Some(scalar);
759 continue;
760 }
761
762 qualifiers.push(variable_qualifier_from_token(
763 &token,
764 line_number,
765 VariableQualifierContext::Tex,
766 )?);
767 }
768
769 Ok(VariableDirective {
770 visibility: None,
771 linkages: Vec::new(),
772 address_space: None,
773 mutability: None,
774 alignment: None,
775 ty,
776 qualifiers,
777 name,
778 array: None,
779 initializer: None,
780 raw: line.trim().to_string(),
781 })
782}
783
784fn parse_state_space_variable(
785 line: &str,
786 line_number: usize,
787) -> Result<Option<ModuleVariableDirective>, PtxParseError> {
788 if !line.trim_end().ends_with(';') {
789 return Ok(None);
790 }
791
792 let without_semicolon = line.trim_end().trim_end_matches(';').trim_end();
793 if without_semicolon.is_empty() {
794 return Ok(None);
795 }
796
797 let (decl_part, initializer_part) = split_initializer(without_semicolon);
798 let initializer = initializer_part.and_then(|init| {
799 let trimmed = init.trim();
800 if trimmed.is_empty() {
801 None
802 } else {
803 Some(trimmed.to_string())
804 }
805 });
806
807 let mut tokens: Vec<String> = decl_part
808 .split_whitespace()
809 .map(|tok| tok.trim().trim_matches(',').to_string())
810 .filter(|tok| !tok.is_empty())
811 .collect();
812
813 if tokens.len() < 2 {
814 return Ok(None);
815 }
816
817 let name_token = tokens.pop().unwrap();
818 if name_token.starts_with('.') {
819 return Ok(None);
820 }
821
822 let (name, array) = split_name_token(&name_token, line_number)?;
823
824 let mut visibility = None;
825 let mut linkages = Vec::new();
826 let mut address_space = None;
827 let mut mutability = None;
828 let mut ty = None;
829 let mut qualifiers: Vec<VariableQualifier> = Vec::new();
830 let mut alignment = None;
831 let mut idx = 0;
832 while idx < tokens.len() {
833 let token = tokens[idx].clone();
834 if token == ".align" {
835 if idx + 1 < tokens.len() {
836 if let Ok(value) = tokens[idx + 1].parse::<u32>() {
837 alignment = Some(value);
838 idx += 2;
839 continue;
840 }
841 }
842 return Err(PtxParseError::InvalidGlobal {
843 line: line_number,
844 message: "malformed .align qualifier on global variable".into(),
845 });
846 }
847
848 if let Some(parsed) = parse_global_visibility(&token) {
849 visibility = Some(parsed);
850 } else if let Some(linkage) = parse_global_linkage(&token) {
851 if !linkages.contains(&linkage) {
852 linkages.push(linkage);
853 }
854 } else if let Some(space) = parse_global_address_space(&token) {
855 address_space = Some(space);
856 } else if token == ".volatile" {
857 return Err(PtxParseError::InvalidGlobal {
858 line: line_number,
859 message: "module-level state space declarations cannot use '.volatile'".into(),
860 });
861 } else if let Some(mutable) = parse_global_mutability(&token) {
862 mutability = Some(mutable);
863 } else if let Some(scalar) = parse_scalar_type(&token) {
864 ty = Some(scalar);
865 } else {
866 qualifiers.push(variable_qualifier_from_token(
867 &token,
868 line_number,
869 VariableQualifierContext::StateSpace,
870 )?);
871 }
872 idx += 1;
873 }
874
875 let parsed_initializer = if let Some(raw_init) = initializer {
876 Some(parse_global_initializer(&raw_init, line_number)?)
877 } else {
878 None
879 };
880
881 let data = ParsedStateVariable {
882 visibility,
883 linkages,
884 address_space,
885 mutability,
886 alignment,
887 ty,
888 qualifiers,
889 name,
890 array,
891 initializer: parsed_initializer,
892 raw: line.trim().to_string(),
893 };
894
895 let directive = match data.address_space {
896 Some(GlobalAddressSpace::Const) => {
897 ModuleVariableDirective::Const(data.into_variable(GlobalAddressSpace::Const))
898 }
899 Some(GlobalAddressSpace::Shared) => {
900 ModuleVariableDirective::Shared(data.into_variable(GlobalAddressSpace::Shared))
901 }
902 Some(GlobalAddressSpace::Global) => {
903 ModuleVariableDirective::Global(data.into_variable(GlobalAddressSpace::Global))
904 }
905 _ => return Ok(None),
906 };
907
908 Ok(Some(directive))
909}
910
911fn parse_function_header(
912 header: &str,
913 keyword: &str,
914 line_number: usize,
915) -> Result<
916 (
917 Vec<FunctionHeaderDirective>,
918 String,
919 Vec<Parameter>,
920 Option<Parameter>,
921 ),
922 PtxParseError,
923> {
924 let kind_pos = header
925 .find(keyword)
926 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
927 line: line_number,
928 message: format!("missing {keyword} keyword"),
929 })?;
930
931 let qualifiers_part = header[..kind_pos].trim();
932 let mut directives = Vec::new();
933 parse_function_header_directives(qualifiers_part, &mut directives, line_number)?;
934
935 let mut after_kind = header[kind_pos + keyword.len()..].trim();
936 let mut return_param = None;
937 if keyword == ".func" {
938 loop {
939 let trimmed = after_kind.trim_start();
940 after_kind = trimmed;
941 if !after_kind.starts_with('(') {
942 break;
943 }
944
945 let (section, remainder) = extract_parenthesized_section(after_kind, line_number)?;
946
947 let looks_like_return_param = section
948 .split(|ch: char| ch.is_whitespace() || ch == ',')
949 .filter(|tok| !tok.is_empty())
950 .any(|tok| {
951 matches!(
952 tok.trim_start_matches('.').to_ascii_lowercase().as_str(),
953 "param" | "reg"
954 )
955 });
956
957 if looks_like_return_param && return_param.is_none() {
958 let params = parse_parameters(§ion, line_number)?;
959 if params.len() > 1 {
960 return Err(PtxParseError::InvalidFunctionHeader {
961 line: line_number,
962 message: "multiple return values are not supported".into(),
963 });
964 }
965 if let Some(param) = params.into_iter().next() {
966 return_param = Some(param);
967 after_kind = remainder;
968 continue;
969 }
970 }
971
972 after_kind = remainder;
973 }
974 }
975
976 let after_kind = after_kind.trim();
977 let (name, params, trailing_after_params) = if let Some(open_paren) = after_kind.find('(') {
978 let signature_part = after_kind[..open_paren].trim();
979 let name = signature_part
980 .split_whitespace()
981 .last()
982 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
983 line: line_number,
984 message: "missing function name".into(),
985 })?
986 .to_string();
987
988 let close_paren =
989 after_kind
990 .rfind(')')
991 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
992 line: line_number,
993 message: "missing ')' in function header".into(),
994 })?;
995
996 if close_paren <= open_paren {
997 return Err(PtxParseError::InvalidFunctionHeader {
998 line: line_number,
999 message: "malformed parameter list".into(),
1000 });
1001 }
1002
1003 let params_raw = &after_kind[open_paren + 1..close_paren];
1004 let params = parse_parameters(params_raw, line_number)?;
1005 let trailing_slice = after_kind[close_paren + 1..].trim();
1006 let trailing = if trailing_slice.is_empty() {
1007 None
1008 } else {
1009 Some(trailing_slice.to_string())
1010 };
1011 (name, params, trailing)
1012 } else {
1013 if keyword == ".func" {
1014 return Err(PtxParseError::InvalidFunctionHeader {
1015 line: line_number,
1016 message: ".func directive requires parameter list".into(),
1017 });
1018 }
1019 let mut parts = after_kind.split_whitespace();
1020 let name_token = parts
1021 .next()
1022 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1023 line: line_number,
1024 message: "missing kernel name".into(),
1025 })?;
1026 let remainder: Vec<&str> = parts.collect();
1027 let trailing = if remainder.is_empty() {
1028 None
1029 } else {
1030 Some(remainder.join(" "))
1031 };
1032 (name_token.to_string(), Vec::new(), trailing)
1033 };
1034
1035 if let Some(extra_tokens) = trailing_after_params {
1036 parse_function_header_directives(&extra_tokens, &mut directives, line_number)?;
1037 }
1038
1039 Ok((directives, name, params, return_param))
1040}
1041
1042fn extract_parenthesized_section<'a>(
1043 input: &'a str,
1044 line_number: usize,
1045) -> Result<(String, &'a str), PtxParseError> {
1046 debug_assert!(input.starts_with('('));
1047
1048 let mut depth = 0i32;
1049 let mut end_idx = None;
1050 for (idx, ch) in input.char_indices() {
1051 match ch {
1052 '(' => depth += 1,
1053 ')' => {
1054 depth -= 1;
1055 if depth == 0 {
1056 end_idx = Some(idx);
1057 break;
1058 }
1059 }
1060 _ => {}
1061 }
1062 }
1063
1064 let end_idx = end_idx.ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1065 line: line_number,
1066 message: "unbalanced parentheses in function header".into(),
1067 })?;
1068
1069 let section = input[1..end_idx].to_string();
1070 let remainder = &input[end_idx + 1..];
1071 Ok((section, remainder))
1072}
1073
1074#[derive(Default)]
1075struct LinkageAccumulator {
1076 seen_extern: bool,
1077 seen_weak: bool,
1078}
1079
1080fn parse_function_header_directives(
1081 segment: &str,
1082 directives: &mut Vec<FunctionHeaderDirective>,
1083 line_number: usize,
1084) -> Result<(), PtxParseError> {
1085 if segment.trim().is_empty() {
1086 return Ok(());
1087 }
1088
1089 let mut tokens: VecDeque<String> = segment
1090 .split_whitespace()
1091 .map(|tok| tok.to_string())
1092 .collect();
1093 let mut linkage = LinkageAccumulator::default();
1094
1095 while let Some(token) = tokens.pop_front() {
1096 let trimmed = token.trim();
1097 if trimmed.is_empty() {
1098 continue;
1099 }
1100
1101 if !trimmed.starts_with('.') {
1102 let cleaned = strip_trailing_delimiters(trimmed);
1103 return Err(PtxParseError::InvalidFunctionHeader {
1104 line: line_number,
1105 message: format!("unexpected token '{}' in function header", cleaned),
1106 });
1107 }
1108
1109 let mut directive_token = trimmed.trim_start_matches('.').to_string();
1110 let ends_with_semicolon = directive_token.contains(';');
1111 directive_token = strip_trailing_delimiters(&directive_token);
1112 let lower = directive_token.to_ascii_lowercase();
1113
1114 match lower.as_str() {
1115 "visible" => {
1116 flush_linkage(&mut linkage, directives);
1117 directives.push(FunctionHeaderDirective::Visibility(
1118 FunctionVisibility::Visible,
1119 ));
1120 }
1121 "hidden" => {
1122 flush_linkage(&mut linkage, directives);
1123 directives.push(FunctionHeaderDirective::Visibility(
1124 FunctionVisibility::Hidden,
1125 ));
1126 }
1127 "extern" => {
1128 linkage.seen_extern = true;
1129 }
1130 "weak" => {
1131 linkage.seen_weak = true;
1132 }
1133 "weakextern" => {
1134 flush_linkage(&mut linkage, directives);
1135 directives.push(FunctionHeaderDirective::Linkage(
1136 FunctionLinkage::WeakExtern,
1137 ));
1138 }
1139 "noreturn" => {
1140 flush_linkage(&mut linkage, directives);
1141 directives.push(FunctionHeaderDirective::NoReturn);
1142 }
1143 "abi_preserve" => {
1144 flush_linkage(&mut linkage, directives);
1145 let value = parse_numeric_argument(&mut tokens, line_number, ".abi_preserve")?;
1146 directives.push(FunctionHeaderDirective::AbiPreserve(value));
1147 }
1148 "abi_preserve_control" => {
1149 flush_linkage(&mut linkage, directives);
1150 let value =
1151 parse_numeric_argument(&mut tokens, line_number, ".abi_preserve_control")?;
1152 directives.push(FunctionHeaderDirective::AbiPreserveControl(value));
1153 }
1154 "maxclusterrank" => {
1155 flush_linkage(&mut linkage, directives);
1156 let value = parse_numeric_argument(&mut tokens, line_number, ".maxclusterrank")?;
1157 directives.push(FunctionHeaderDirective::MaxClusterRank(value));
1158 }
1159 "blocksareclusters" => {
1160 flush_linkage(&mut linkage, directives);
1161 directives.push(FunctionHeaderDirective::BlocksAreClusters);
1162 }
1163 "explicitcluster" => {
1164 flush_linkage(&mut linkage, directives);
1165 let dims = parse_dim3_arguments(&mut tokens, line_number, ".explicitcluster")?;
1166 directives.push(FunctionHeaderDirective::ExplicitCluster(dims));
1167 }
1168 "reqnctapercluster" => {
1169 flush_linkage(&mut linkage, directives);
1170 let dims = parse_dim3_arguments(&mut tokens, line_number, ".reqnctapercluster")?;
1171 directives.push(FunctionHeaderDirective::ReqNctaPerCluster(dims));
1172 }
1173 "maxnreg" => {
1174 flush_linkage(&mut linkage, directives);
1175 let value = parse_numeric_argument(&mut tokens, line_number, ".maxnreg")?;
1176 directives.push(FunctionHeaderDirective::MaxNReg(value));
1177 }
1178 "maxntid" => {
1179 flush_linkage(&mut linkage, directives);
1180 let dims = parse_dim3_arguments(&mut tokens, line_number, ".maxntid")?;
1181 directives.push(FunctionHeaderDirective::MaxNTid(dims));
1182 }
1183 "minnctapersm" => {
1184 flush_linkage(&mut linkage, directives);
1185 let value = parse_numeric_argument(&mut tokens, line_number, ".minnctapersm")?;
1186 directives.push(FunctionHeaderDirective::MinNCtaPerSm(value));
1187 }
1188 "reqntid" => {
1189 flush_linkage(&mut linkage, directives);
1190 let dims = parse_dim3_arguments(&mut tokens, line_number, ".reqntid")?;
1191 directives.push(FunctionHeaderDirective::ReqNTid(dims));
1192 }
1193 "maxnctapersm" => {
1194 flush_linkage(&mut linkage, directives);
1195 let value = parse_numeric_argument(&mut tokens, line_number, ".maxnctapersm")?;
1196 directives.push(FunctionHeaderDirective::MaxNCtaPerSm(value));
1197 }
1198 "pragma" => {
1199 flush_linkage(&mut linkage, directives);
1200 let args = parse_pragma_arguments(&mut tokens, line_number)?;
1201 directives.push(FunctionHeaderDirective::Pragma(args));
1202 }
1203 _ => {
1204 let mut original = String::from(".");
1205 original.push_str(&directive_token);
1206 return Err(PtxParseError::InvalidFunctionHeader {
1207 line: line_number,
1208 message: format!("unrecognised function header directive '{}'", original),
1209 });
1210 }
1211 }
1212
1213 if ends_with_semicolon {
1214 flush_linkage(&mut linkage, directives);
1215 }
1216 }
1217
1218 flush_linkage(&mut linkage, directives);
1219 Ok(())
1220}
1221
1222fn flush_linkage(linkage: &mut LinkageAccumulator, directives: &mut Vec<FunctionHeaderDirective>) {
1223 if linkage.seen_extern || linkage.seen_weak {
1224 let directive = match (linkage.seen_extern, linkage.seen_weak) {
1225 (true, true) => FunctionLinkage::WeakExtern,
1226 (true, false) => FunctionLinkage::Extern,
1227 (false, true) => FunctionLinkage::Weak,
1228 (false, false) => return,
1229 };
1230 directives.push(FunctionHeaderDirective::Linkage(directive));
1231 *linkage = LinkageAccumulator::default();
1232 }
1233}
1234
1235fn strip_trailing_delimiters(value: &str) -> String {
1236 value
1237 .trim_end_matches(|ch| ch == ',' || ch == ';')
1238 .to_string()
1239}
1240
1241fn parse_numeric_argument(
1242 tokens: &mut VecDeque<String>,
1243 line_number: usize,
1244 directive: &str,
1245) -> Result<u32, PtxParseError> {
1246 let token = tokens
1247 .pop_front()
1248 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1249 line: line_number,
1250 message: format!("missing numeric argument for {directive}"),
1251 })?;
1252 let (value, has_comma) = parse_numeric_token(&token, line_number, directive)?;
1253 if has_comma {
1254 return Err(PtxParseError::InvalidFunctionHeader {
1255 line: line_number,
1256 message: format!("unexpected ',' after argument for {directive}"),
1257 });
1258 }
1259 Ok(value)
1260}
1261
1262fn parse_dim3_arguments(
1263 tokens: &mut VecDeque<String>,
1264 line_number: usize,
1265 directive: &str,
1266) -> Result<FunctionDim3, PtxParseError> {
1267 let first = tokens
1268 .pop_front()
1269 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1270 line: line_number,
1271 message: format!("missing arguments for {directive}"),
1272 })?;
1273 let (x, mut expect_more) = parse_numeric_token(&first, line_number, directive)?;
1274 let mut y = None;
1275 let mut z = None;
1276
1277 if expect_more {
1278 let second = tokens
1279 .pop_front()
1280 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1281 line: line_number,
1282 message: format!("missing 'y' argument for {directive}"),
1283 })?;
1284 let (value, more) = parse_numeric_token(&second, line_number, directive)?;
1285 y = Some(value);
1286 expect_more = more;
1287 } else {
1288 expect_more = false;
1289 }
1290
1291 if expect_more {
1292 let third = tokens
1293 .pop_front()
1294 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1295 line: line_number,
1296 message: format!("missing 'z' argument for {directive}"),
1297 })?;
1298 let (value, more) = parse_numeric_token(&third, line_number, directive)?;
1299 if more {
1300 return Err(PtxParseError::InvalidFunctionHeader {
1301 line: line_number,
1302 message: format!("unexpected extra arguments for {directive}"),
1303 });
1304 }
1305 z = Some(value);
1306 }
1307
1308 Ok(FunctionDim3 { x, y, z })
1309}
1310
1311fn parse_numeric_token(
1312 token: &str,
1313 line_number: usize,
1314 directive: &str,
1315) -> Result<(u32, bool), PtxParseError> {
1316 let mut trimmed = token.trim();
1317 let mut has_semicolon = false;
1318 if trimmed.ends_with(';') {
1319 has_semicolon = true;
1320 trimmed = trimmed.trim_end_matches(';').trim_end();
1321 }
1322
1323 let mut has_comma = false;
1324 if trimmed.ends_with(',') {
1325 has_comma = true;
1326 trimmed = trimmed.trim_end_matches(',').trim_end();
1327 }
1328
1329 if trimmed.is_empty() {
1330 return Err(PtxParseError::InvalidFunctionHeader {
1331 line: line_number,
1332 message: format!("expected numeric value for {directive}"),
1333 });
1334 }
1335
1336 let value = trimmed
1337 .parse::<u32>()
1338 .map_err(|_| PtxParseError::InvalidFunctionHeader {
1339 line: line_number,
1340 message: format!("invalid numeric value '{}' in {}", trimmed, directive),
1341 })?;
1342
1343 if has_semicolon && has_comma {
1344 return Err(PtxParseError::InvalidFunctionHeader {
1345 line: line_number,
1346 message: format!(
1347 "unexpected ';' after comma-separated value in {}",
1348 directive
1349 ),
1350 });
1351 }
1352
1353 Ok((value, has_comma))
1354}
1355
1356fn parse_pragma_arguments(
1357 tokens: &mut VecDeque<String>,
1358 line_number: usize,
1359) -> Result<Vec<String>, PtxParseError> {
1360 let mut args = Vec::new();
1361 let mut found_semicolon = false;
1362
1363 while let Some(token) = tokens.pop_front() {
1364 let trimmed = token.trim();
1365 if trimmed.is_empty() {
1366 continue;
1367 }
1368 let has_semicolon = trimmed.contains(';');
1369 let cleaned = trimmed.trim_end_matches(';').to_string();
1370 if !cleaned.is_empty() {
1371 args.push(cleaned);
1372 }
1373 if has_semicolon {
1374 found_semicolon = true;
1375 break;
1376 }
1377 }
1378
1379 if !found_semicolon {
1380 return Err(PtxParseError::InvalidFunctionHeader {
1381 line: line_number,
1382 message: ".pragma directive missing terminating ';'".into(),
1383 });
1384 }
1385
1386 if args.is_empty() {
1387 return Err(PtxParseError::InvalidFunctionHeader {
1388 line: line_number,
1389 message: "expected arguments after .pragma".into(),
1390 });
1391 }
1392
1393 Ok(args)
1394}
1395
1396fn parse_parameters(raw: &str, line_number: usize) -> Result<Vec<Parameter>, PtxParseError> {
1397 let mut params = Vec::new();
1398 let mut current = String::new();
1399 let mut depth = 0i32;
1400
1401 for ch in raw.chars() {
1402 match ch {
1403 '(' => {
1404 depth += 1;
1405 current.push(ch);
1406 }
1407 ')' => {
1408 depth -= 1;
1409 current.push(ch);
1410 }
1411 ',' if depth == 0 => {
1412 if let Some(param) = finalize_param(¤t, line_number)? {
1413 params.push(param);
1414 }
1415 current.clear();
1416 }
1417 _ => current.push(ch),
1418 }
1419 }
1420
1421 if !current.trim().is_empty() {
1422 if let Some(param) = finalize_param(¤t, line_number)? {
1423 params.push(param);
1424 }
1425 }
1426
1427 if depth != 0 {
1428 return Err(PtxParseError::InvalidFunctionHeader {
1429 line: line_number,
1430 message: "unbalanced parentheses in parameter list".into(),
1431 });
1432 }
1433
1434 Ok(params)
1435}
1436
1437fn finalize_param(raw: &str, line_number: usize) -> Result<Option<Parameter>, PtxParseError> {
1438 let trimmed = raw.trim();
1439 if trimmed.is_empty() {
1440 return Ok(None);
1441 }
1442
1443 let mut tokens: Vec<String> = trimmed
1444 .split_whitespace()
1445 .map(|tok| tok.trim_matches(',').trim_end_matches(';').to_string())
1446 .filter(|tok| !tok.is_empty())
1447 .collect();
1448
1449 if tokens.is_empty() {
1450 return Err(PtxParseError::InvalidFunctionHeader {
1451 line: line_number,
1452 message: format!("unable to parse parameter '{}': missing name", raw.trim()),
1453 });
1454 }
1455
1456 let name_token = tokens.pop().unwrap();
1457 let specifiers_list = tokens.clone();
1458 let (base_name, array_from_name) = split_name_token(&name_token, line_number)?;
1459
1460 let mut storage = None;
1461 let mut alignment = None;
1462 let mut ty = None;
1463 let mut qualifiers = ParameterQualifiers::default();
1464 let mut pointer: Option<PointerQualifier> = None;
1465 let mut idx = 0;
1466
1467 while idx < tokens.len() {
1468 let token = tokens[idx].clone();
1469 let trimmed = token.trim();
1470 let lower = trimmed.trim_start_matches('.').to_ascii_lowercase();
1471
1472 match lower.as_str() {
1473 "param" => {
1474 storage = Some(ParameterStorage::Param);
1475 idx += 1;
1476 continue;
1477 }
1478 "align" => {
1479 let next =
1480 tokens
1481 .get(idx + 1)
1482 .ok_or_else(|| PtxParseError::InvalidFunctionHeader {
1483 line: line_number,
1484 message: "expected alignment value after .align".into(),
1485 })?;
1486 let value = next.trim_matches(',').parse::<u32>().map_err(|_| {
1487 PtxParseError::InvalidFunctionHeader {
1488 line: line_number,
1489 message: format!("invalid alignment value '{}'", next),
1490 }
1491 })?;
1492 alignment = Some(value);
1493 idx += 2;
1494 continue;
1495 }
1496 "const" => {
1497 if let Some(ptr) = pointer.as_mut() {
1498 if ptr.address_space.is_none() {
1499 ptr.address_space = Some(PointerAddressSpace::Const);
1500 idx += 1;
1501 continue;
1502 }
1503 }
1504 qualifiers.is_const = true;
1505 idx += 1;
1506 continue;
1507 }
1508 "volatile" => {
1509 qualifiers.is_volatile = true;
1510 idx += 1;
1511 continue;
1512 }
1513 "restrict" => {
1514 qualifiers.is_restrict = true;
1515 idx += 1;
1516 continue;
1517 }
1518 "noalias" => {
1519 qualifiers.is_noalias = true;
1520 idx += 1;
1521 continue;
1522 }
1523 "ptr" => {
1524 pointer.get_or_insert_with(PointerQualifier::default);
1525 idx += 1;
1526 continue;
1527 }
1528 "global" => {
1529 if let Some(ptr) = pointer.as_mut() {
1530 ptr.address_space = Some(PointerAddressSpace::Global);
1531 idx += 1;
1532 continue;
1533 }
1534 }
1535 "shared" => {
1536 if let Some(ptr) = pointer.as_mut() {
1537 ptr.address_space = Some(PointerAddressSpace::Shared);
1538 idx += 1;
1539 continue;
1540 }
1541 }
1542 "local" => {
1543 if let Some(ptr) = pointer.as_mut() {
1544 ptr.address_space = Some(PointerAddressSpace::Local);
1545 idx += 1;
1546 continue;
1547 }
1548 }
1549 "reg" => {
1550 idx += 1;
1551 continue;
1552 }
1553 _ => {}
1554 }
1555
1556 if let Some(scalar) = parse_scalar_type(&token) {
1557 ty = Some(scalar);
1558 } else {
1559 return Err(PtxParseError::InvalidFunctionHeader {
1560 line: line_number,
1561 message: format!("unrecognised parameter qualifier '{}'", token),
1562 });
1563 }
1564 idx += 1;
1565 }
1566
1567 if let Some(ptr) = pointer {
1568 qualifiers.pointer = Some(ptr);
1569 }
1570
1571 Ok(Some(Parameter {
1572 name: base_name,
1573 storage,
1574 alignment,
1575 ty,
1576 qualifiers,
1577 array: array_from_name,
1578 specifiers: specifiers_list
1579 .into_iter()
1580 .map(ParameterSpecifier::new)
1581 .collect(),
1582 raw: trimmed.to_string(),
1583 }))
1584}
1585
1586fn parse_function_stmt(
1587 content: &str,
1588 comment: Option<String>,
1589 line_number: usize,
1590 in_declaration: &mut bool,
1591) -> Result<FunctionBodyItem, PtxParseError> {
1592 let trimmed = content.trim();
1593
1594 if trimmed.ends_with(':') {
1595 *in_declaration = false;
1596 return Ok(FunctionBodyItem::Statement(FunctionStatement::Label(
1597 trimmed.trim_end_matches(':').to_string(),
1598 )));
1599 }
1600
1601 if trimmed.starts_with('.') || trimmed.starts_with("@@") {
1602 return parse_function_directive_stmt(trimmed, comment, line_number, in_declaration);
1603 }
1604
1605 let instruction = parse_instruction(trimmed, comment, line_number)?;
1606 *in_declaration = false;
1607 Ok(FunctionBodyItem::Statement(FunctionStatement::Instruction(
1608 instruction,
1609 )))
1610}
1611
1612fn parse_function_directive_stmt(
1613 line: &str,
1614 comment: Option<String>,
1615 line_number: usize,
1616 in_declaration: &mut bool,
1617) -> Result<FunctionBodyItem, PtxParseError> {
1618 let trimmed = line.trim();
1619 let without_semicolon = trimmed
1620 .strip_suffix(';')
1621 .map(|s| s.trim_end())
1622 .unwrap_or(trimmed);
1623
1624 if without_semicolon.is_empty() {
1625 return Err(PtxParseError::InvalidDirective {
1626 line: line_number,
1627 message: "empty directive".into(),
1628 });
1629 }
1630
1631 let mut parts = without_semicolon.split_whitespace();
1632 let keyword = parts.next().unwrap();
1633 let normalized = keyword.trim_start_matches('.').to_ascii_lowercase();
1634 let arguments: Vec<String> = parts.map(|tok| tok.to_string()).collect();
1635
1636 if keyword.starts_with("@@") {
1637 let directive = DwarfDirective {
1638 keyword: keyword.to_string(),
1639 arguments,
1640 comment,
1641 raw: trimmed.to_string(),
1642 };
1643 if *in_declaration {
1644 return Ok(FunctionBodyItem::Entry(FunctionEntryDirective::Dwarf(
1645 directive,
1646 )));
1647 }
1648 *in_declaration = false;
1649 return Ok(FunctionBodyItem::Statement(FunctionStatement::Directive(
1650 StatementDirective::Dwarf(directive),
1651 )));
1652 }
1653
1654 match normalized.as_str() {
1655 "reg" => {
1656 let directive = parse_register_declaration(
1657 keyword,
1658 without_semicolon,
1659 comment,
1660 trimmed.to_string(),
1661 line_number,
1662 )?;
1663 Ok(FunctionBodyItem::Entry(FunctionEntryDirective::Reg(
1664 directive,
1665 )))
1666 }
1667 "local" | "param" | "shared" => {
1668 let kind = match normalized.as_str() {
1669 "local" => FunctionDeclarationKind::Local,
1670 "param" => FunctionDeclarationKind::Param,
1671 "shared" => FunctionDeclarationKind::Shared,
1672 _ => unreachable!(),
1673 };
1674
1675 let directive = GenericFunctionDeclaration {
1676 kind,
1677 keyword: keyword.to_string(),
1678 arguments,
1679 comment,
1680 raw: trimmed.to_string(),
1681 };
1682
1683 let entry = match kind {
1684 FunctionDeclarationKind::Local => FunctionEntryDirective::Local(directive),
1685 FunctionDeclarationKind::Param => FunctionEntryDirective::Param(directive),
1686 FunctionDeclarationKind::Shared => FunctionEntryDirective::Shared(directive),
1687 _ => unreachable!(),
1688 };
1689
1690 Ok(FunctionBodyItem::Entry(entry))
1691 }
1692 "pragma" => {
1693 let mut pragma_arguments = Vec::new();
1694 for token in &arguments {
1695 pragma_arguments.push(unquote_string(token, line_number)?);
1696 }
1697
1698 let directive = PragmaDirective {
1699 arguments: pragma_arguments,
1700 comment,
1701 raw: trimmed.to_string(),
1702 };
1703
1704 if *in_declaration {
1705 Ok(FunctionBodyItem::Entry(FunctionEntryDirective::Pragma(
1706 directive,
1707 )))
1708 } else {
1709 *in_declaration = false;
1710 Ok(FunctionBodyItem::Statement(FunctionStatement::Directive(
1711 StatementDirective::Pragma(directive),
1712 )))
1713 }
1714 }
1715 "loc" => {
1716 if arguments.len() < 3 {
1717 return Err(PtxParseError::InvalidDirective {
1718 line: line_number,
1719 message: ".loc directive expects file, line, and column arguments".into(),
1720 });
1721 }
1722
1723 let file_index =
1724 arguments[0]
1725 .parse::<u32>()
1726 .map_err(|_| PtxParseError::InvalidDirective {
1727 line: line_number,
1728 message: format!("invalid .loc file index '{}'", arguments[0]),
1729 })?;
1730 let line_value =
1731 arguments[1]
1732 .parse::<u32>()
1733 .map_err(|_| PtxParseError::InvalidDirective {
1734 line: line_number,
1735 message: format!("invalid .loc line '{}'", arguments[1]),
1736 })?;
1737 let column_value =
1738 arguments[2]
1739 .parse::<u32>()
1740 .map_err(|_| PtxParseError::InvalidDirective {
1741 line: line_number,
1742 message: format!("invalid .loc column '{}'", arguments[2]),
1743 })?;
1744
1745 let options = if arguments.len() > 3 {
1746 arguments[3..].to_vec()
1747 } else {
1748 Vec::new()
1749 };
1750
1751 let directive = LocationDirective {
1752 file_index,
1753 line: line_value,
1754 column: column_value,
1755 options,
1756 comment,
1757 raw: trimmed.to_string(),
1758 };
1759
1760 if *in_declaration {
1761 Ok(FunctionBodyItem::Entry(FunctionEntryDirective::Loc(
1762 directive,
1763 )))
1764 } else {
1765 *in_declaration = false;
1766 Ok(FunctionBodyItem::Statement(FunctionStatement::Directive(
1767 StatementDirective::Loc(directive),
1768 )))
1769 }
1770 }
1771 "section" => {
1772 if arguments.is_empty() {
1773 return Err(PtxParseError::InvalidDirective {
1774 line: line_number,
1775 message: ".section directive requires a name".into(),
1776 });
1777 }
1778 let name = arguments[0].clone();
1779 let rest = if arguments.len() > 1 {
1780 arguments[1..].to_vec()
1781 } else {
1782 Vec::new()
1783 };
1784 let directive = StatementSectionDirective {
1785 name,
1786 arguments: rest,
1787 comment,
1788 raw: trimmed.to_string(),
1789 };
1790 *in_declaration = false;
1791 Ok(FunctionBodyItem::Statement(FunctionStatement::Directive(
1792 StatementDirective::Section(directive),
1793 )))
1794 }
1795 _ => Err(PtxParseError::InvalidDirective {
1796 line: line_number,
1797 message: format!("unsupported directive '{keyword}'"),
1798 }),
1799 }
1800}
1801
1802fn parse_instruction(
1803 line: &str,
1804 comment: Option<String>,
1805 line_number: usize,
1806) -> Result<Instruction, PtxParseError> {
1807 let raw_line = line.trim().to_string();
1808 let mut stmt = line.trim_end_matches(';').trim();
1809 if stmt.is_empty() {
1810 return Err(PtxParseError::InvalidInstruction {
1811 line: line_number,
1812 message: "empty instruction".into(),
1813 });
1814 }
1815
1816 let mut predicate = None;
1817 if stmt.starts_with('@') {
1818 if let Some((pred, rest)) = stmt.split_once(' ') {
1819 predicate = Some(pred.trim_start_matches('@').to_string());
1820 stmt = rest.trim();
1821 } else {
1822 return Err(PtxParseError::InvalidInstruction {
1823 line: line_number,
1824 message: "predicate missing instruction".into(),
1825 });
1826 }
1827 }
1828
1829 let mut parts = stmt.split_whitespace();
1830 let opcode_raw = parts
1831 .next()
1832 .ok_or_else(|| PtxParseError::InvalidInstruction {
1833 line: line_number,
1834 message: "missing opcode".into(),
1835 })?;
1836
1837 let opcode_text = opcode_raw
1838 .split('.')
1839 .next()
1840 .unwrap_or(opcode_raw)
1841 .to_string();
1842
1843 let modifier_texts: Vec<String> = opcode_raw
1844 .split('.')
1845 .skip(1)
1846 .filter(|s| !s.is_empty())
1847 .map(|s| s.to_string())
1848 .collect();
1849
1850 let opcode_kind =
1851 classify_opcode(&opcode_text).ok_or_else(|| PtxParseError::InvalidInstruction {
1852 line: line_number,
1853 message: format!("unknown opcode '{opcode_text}'"),
1854 })?;
1855 let modifiers = parse_instruction_modifiers(&modifier_texts, line_number)?;
1856
1857 let operands_raw = parts.collect::<Vec<_>>().join(" ");
1858 let operands = parse_operands(&operands_raw, line_number)?;
1859
1860 Ok(Instruction {
1861 predicate,
1862 opcode: InstructionOpcode {
1863 kind: opcode_kind,
1864 modifiers,
1865 },
1866 operands,
1867 comment,
1868 raw: raw_line,
1869 })
1870}
1871
1872fn parse_instruction_modifiers(
1873 modifier_texts: &[String],
1874 line_number: usize,
1875) -> Result<Vec<ModifierKind>, PtxParseError> {
1876 let mut modifiers = Vec::new();
1877 let mut index = 0;
1878
1879 while index < modifier_texts.len() {
1880 let current = &modifier_texts[index];
1881 let trimmed = current.trim_start_matches('.');
1882 let lower = trimmed.to_ascii_lowercase();
1883 let primary = match lower.split("::").next() {
1884 Some(token) if !token.is_empty() => token,
1885 _ => lower.as_str(),
1886 };
1887
1888 if trimmed.eq_ignore_ascii_case("to") {
1889 if let Some(next) = modifier_texts.get(index + 1) {
1890 let next_trimmed = next.trim_start_matches('.');
1891 if let Some(space) =
1892 parse_address_space_modifier(&next_trimmed.to_ascii_lowercase())
1893 {
1894 modifiers.push(ModifierKind::Conversion(space));
1895 index += 2;
1896 continue;
1897 }
1898 }
1899
1900 return Err(PtxParseError::InvalidInstruction {
1901 line: line_number,
1902 message: "conversion modifier missing address space".into(),
1903 });
1904 }
1905
1906 if let Some(modifier) = classify_modifier(primary) {
1907 modifiers.push(modifier);
1908 index += 1;
1909 continue;
1910 }
1911
1912 if let Some(width) = parse_vector_width_modifier(primary) {
1913 modifiers.push(ModifierKind::VectorWidth(width));
1914 index += 1;
1915 continue;
1916 }
1917
1918 if primary == "wide" {
1919 modifiers.push(ModifierKind::Wide);
1920 index += 1;
1921 continue;
1922 }
1923
1924 return Err(PtxParseError::InvalidInstruction {
1925 line: line_number,
1926 message: format!("unrecognised modifier '{}'", current),
1927 });
1928 }
1929
1930 Ok(modifiers)
1931}
1932
1933fn parse_operands(raw: &str, line_number: usize) -> Result<Vec<Operand>, PtxParseError> {
1934 if raw.trim().is_empty() {
1935 return Ok(Vec::new());
1936 }
1937
1938 let mut operands = Vec::new();
1939 let mut current = String::new();
1940 let mut paren_depth = 0usize;
1941 let mut bracket_depth = 0usize;
1942
1943 let push_current =
1944 |store: &mut Vec<Operand>, token: &mut String| -> Result<(), PtxParseError> {
1945 let trimmed = token.trim().trim_end_matches(';').trim();
1946 if !trimmed.is_empty() {
1947 store.push(parse_operand(trimmed, line_number)?);
1948 }
1949 token.clear();
1950 Ok(())
1951 };
1952
1953 for ch in raw.chars() {
1954 match ch {
1955 ',' if paren_depth == 0 && bracket_depth == 0 => {
1956 push_current(&mut operands, &mut current)?;
1957 }
1958 '(' => {
1959 paren_depth += 1;
1960 current.push(ch);
1961 }
1962 ')' => {
1963 if paren_depth == 0 {
1964 return Err(PtxParseError::InvalidInstruction {
1965 line: line_number,
1966 message: "unmatched ')' in operand list".into(),
1967 });
1968 }
1969 paren_depth -= 1;
1970 current.push(ch);
1971 }
1972 '[' => {
1973 bracket_depth += 1;
1974 current.push(ch);
1975 }
1976 ']' => {
1977 if bracket_depth == 0 {
1978 return Err(PtxParseError::InvalidInstruction {
1979 line: line_number,
1980 message: "unmatched ']' in operand list".into(),
1981 });
1982 }
1983 bracket_depth -= 1;
1984 current.push(ch);
1985 }
1986 _ => current.push(ch),
1987 }
1988 }
1989
1990 if paren_depth != 0 {
1991 return Err(PtxParseError::InvalidInstruction {
1992 line: line_number,
1993 message: "unterminated '(' in operand list".into(),
1994 });
1995 }
1996
1997 if bracket_depth != 0 {
1998 return Err(PtxParseError::InvalidInstruction {
1999 line: line_number,
2000 message: "unterminated '[' in operand list".into(),
2001 });
2002 }
2003
2004 push_current(&mut operands, &mut current)?;
2005
2006 Ok(operands)
2007}
2008
2009fn parse_operand(token: &str, line_number: usize) -> Result<Operand, PtxParseError> {
2010 if token.is_empty() {
2011 return Err(PtxParseError::InvalidInstruction {
2012 line: line_number,
2013 message: "empty operand".into(),
2014 });
2015 }
2016
2017 if token.starts_with('[') && token.ends_with(']') {
2018 return parse_memory_operand(token, line_number);
2019 }
2020
2021 if token.starts_with('(') && token.ends_with(')') {
2022 let inner = token[1..token.len() - 1].trim();
2023 let items = if inner.is_empty() {
2024 Vec::new()
2025 } else {
2026 inner
2027 .split(',')
2028 .map(|part| part.trim())
2029 .filter(|part| !part.is_empty())
2030 .map(|part| part.to_string())
2031 .collect()
2032 };
2033 return Ok(Operand::Parenthesized(items));
2034 }
2035
2036 if let Some(paren_index) = token.find('(') {
2037 if paren_index > 0 && token.ends_with(')') {
2038 let name_part = token[..paren_index].trim();
2039 if is_symbol_token(name_part) {
2040 let args_inner = token[paren_index + 1..token.len() - 1].trim();
2041 let arguments = if args_inner.is_empty() {
2042 Vec::new()
2043 } else {
2044 args_inner
2045 .split(',')
2046 .map(|arg| arg.trim())
2047 .filter(|arg| !arg.is_empty())
2048 .map(|arg| arg.to_string())
2049 .collect()
2050 };
2051 return Ok(Operand::CallTarget {
2052 name: name_part.to_string(),
2053 arguments,
2054 });
2055 }
2056 }
2057 }
2058
2059 if token.starts_with('%') {
2060 return Ok(Operand::Register(token.to_string()));
2061 }
2062
2063 if is_numeric_literal(token) {
2064 return Ok(Operand::Immediate(token.to_string()));
2065 }
2066
2067 if is_symbol_token(token) {
2068 return Ok(Operand::Symbol(token.to_string()));
2069 }
2070
2071 Err(PtxParseError::InvalidInstruction {
2072 line: line_number,
2073 message: format!("unknown operand '{token}'"),
2074 })
2075}
2076
2077fn parse_memory_operand(token: &str, line_number: usize) -> Result<Operand, PtxParseError> {
2078 if token.len() < 2 {
2079 return Err(PtxParseError::InvalidInstruction {
2080 line: line_number,
2081 message: format!("invalid memory operand: {token}"),
2082 });
2083 }
2084
2085 let inner = token[1..token.len() - 1].trim();
2086 if inner.is_empty() {
2087 return Err(PtxParseError::InvalidInstruction {
2088 line: line_number,
2089 message: "memory operand missing address expression".into(),
2090 });
2091 }
2092
2093 let tokens = tokenize_address(inner, line_number)?;
2094 let memory = build_memory_operand(&tokens, line_number)?;
2095
2096 Ok(Operand::Memory(memory))
2097}
2098
2099#[derive(Debug, Clone)]
2100enum AddressToken {
2101 Register(String),
2102 Symbol(String),
2103 Immediate(String),
2104 Plus,
2105 Minus,
2106 Star,
2107}
2108
2109fn tokenize_address(expr: &str, line_number: usize) -> Result<Vec<AddressToken>, PtxParseError> {
2110 let mut tokens = Vec::new();
2111 let mut current = String::new();
2112
2113 let flush_current =
2114 |current: &mut String, tokens: &mut Vec<AddressToken>| -> Result<(), PtxParseError> {
2115 if current.is_empty() {
2116 return Ok(());
2117 }
2118 let token = classify_address_token(current.as_str(), line_number)?;
2119 tokens.push(token);
2120 current.clear();
2121 Ok(())
2122 };
2123
2124 for ch in expr.chars() {
2125 match ch {
2126 '+' => {
2127 flush_current(&mut current, &mut tokens)?;
2128 tokens.push(AddressToken::Plus);
2129 }
2130 '-' => {
2131 flush_current(&mut current, &mut tokens)?;
2132 tokens.push(AddressToken::Minus);
2133 }
2134 '*' => {
2135 flush_current(&mut current, &mut tokens)?;
2136 tokens.push(AddressToken::Star);
2137 }
2138 ch if ch.is_whitespace() => {
2139 flush_current(&mut current, &mut tokens)?;
2140 }
2141 _ => current.push(ch),
2142 }
2143 }
2144
2145 flush_current(&mut current, &mut tokens)?;
2146
2147 if tokens.is_empty() {
2148 return Err(PtxParseError::InvalidInstruction {
2149 line: line_number,
2150 message: "empty memory address expression".into(),
2151 });
2152 }
2153
2154 Ok(tokens)
2155}
2156
2157fn classify_address_token(token: &str, line_number: usize) -> Result<AddressToken, PtxParseError> {
2158 if token.starts_with('%') && token.len() > 1 {
2159 return Ok(AddressToken::Register(token.to_string()));
2160 }
2161 if is_numeric_literal(token) {
2162 return Ok(AddressToken::Immediate(token.to_string()));
2163 }
2164 if is_symbol_token(token) {
2165 return Ok(AddressToken::Symbol(token.to_string()));
2166 }
2167
2168 Err(PtxParseError::InvalidInstruction {
2169 line: line_number,
2170 message: format!("invalid memory address token '{token}'"),
2171 })
2172}
2173
2174fn build_memory_operand(
2175 tokens: &[AddressToken],
2176 line_number: usize,
2177) -> Result<MemoryOperand, PtxParseError> {
2178 let mut base: Option<AddressBase> = None;
2179 let mut displacements = Vec::new();
2180
2181 let mut pending_sign = AddressSign::Positive;
2182 let mut sign_set = false;
2183
2184 let mut index = 0;
2185 while index < tokens.len() {
2186 match &tokens[index] {
2187 AddressToken::Plus => {
2188 if sign_set {
2189 } else {
2191 pending_sign = AddressSign::Positive;
2192 sign_set = true;
2193 }
2194 index += 1;
2195 }
2196 AddressToken::Minus => {
2197 if sign_set {
2198 pending_sign = pending_sign.negate();
2199 } else {
2200 pending_sign = AddressSign::Negative;
2201 sign_set = true;
2202 }
2203 index += 1;
2204 }
2205 AddressToken::Star => {
2206 return Err(PtxParseError::InvalidInstruction {
2207 line: line_number,
2208 message: "unexpected '*' in memory address expression".into(),
2209 });
2210 }
2211 AddressToken::Register(name) => {
2212 let sign = if sign_set {
2213 pending_sign
2214 } else {
2215 AddressSign::Positive
2216 };
2217 pending_sign = AddressSign::Positive;
2218 sign_set = false;
2219
2220 let mut scale = None;
2221 if index + 1 < tokens.len() {
2222 if matches!(tokens[index + 1], AddressToken::Star) {
2223 if index + 2 >= tokens.len() {
2224 return Err(PtxParseError::InvalidInstruction {
2225 line: line_number,
2226 message: "missing scale after '*' in memory address".into(),
2227 });
2228 }
2229 match &tokens[index + 2] {
2230 AddressToken::Immediate(value) => {
2231 scale = Some(value.clone());
2232 index += 2;
2233 }
2234 _ => {
2235 return Err(PtxParseError::InvalidInstruction {
2236 line: line_number,
2237 message: "expected immediate scale after '*' in memory address"
2238 .into(),
2239 });
2240 }
2241 }
2242 }
2243 }
2244
2245 let displacement = AddressDisplacement {
2246 sign,
2247 kind: AddressDisplacementKind::Register {
2248 register: name.clone(),
2249 scale: scale.clone(),
2250 },
2251 };
2252
2253 let can_be_base =
2254 base.is_none() && matches!(sign, AddressSign::Positive) && scale.is_none();
2255 if can_be_base {
2256 base = Some(AddressBase::Register(name.clone()));
2257 } else {
2258 displacements.push(displacement);
2259 }
2260
2261 index += 1;
2262 }
2263 AddressToken::Symbol(name) => {
2264 let sign = if sign_set {
2265 pending_sign
2266 } else {
2267 AddressSign::Positive
2268 };
2269 pending_sign = AddressSign::Positive;
2270 sign_set = false;
2271
2272 let displacement = AddressDisplacement {
2273 sign,
2274 kind: AddressDisplacementKind::Symbol(name.clone()),
2275 };
2276
2277 if base.is_none() && matches!(sign, AddressSign::Positive) {
2278 base = Some(AddressBase::Symbol(name.clone()));
2279 } else {
2280 displacements.push(displacement);
2281 }
2282
2283 index += 1;
2284 }
2285 AddressToken::Immediate(value) => {
2286 let sign = if sign_set {
2287 pending_sign
2288 } else {
2289 AddressSign::Positive
2290 };
2291 pending_sign = AddressSign::Positive;
2292 sign_set = false;
2293
2294 displacements.push(AddressDisplacement {
2295 sign,
2296 kind: AddressDisplacementKind::Immediate(value.clone()),
2297 });
2298
2299 index += 1;
2300 }
2301 }
2302 }
2303
2304 if sign_set {
2305 return Err(PtxParseError::InvalidInstruction {
2306 line: line_number,
2307 message: "dangling sign in memory address expression".into(),
2308 });
2309 }
2310
2311 Ok(MemoryOperand {
2312 base,
2313 displacements,
2314 })
2315}
2316
2317fn classify_opcode(opcode: &str) -> Option<OpcodeKind> {
2318 match opcode.to_ascii_lowercase().as_str() {
2319 "abs" => Some(OpcodeKind::Abs),
2320 "activemask" => Some(OpcodeKind::Activemask),
2321 "add" => Some(OpcodeKind::Add),
2322 "addc" => Some(OpcodeKind::Addc),
2323 "alloca" => Some(OpcodeKind::Alloca),
2324 "and" => Some(OpcodeKind::And),
2325 "applypriority" => Some(OpcodeKind::Applypriority),
2326 "atom" => Some(OpcodeKind::Atom),
2327 "bar" => Some(OpcodeKind::Bar),
2328 "barrier" => Some(OpcodeKind::Barrier),
2329 "bfe" => Some(OpcodeKind::Bfe),
2330 "bfi" => Some(OpcodeKind::Bfi),
2331 "bfind" => Some(OpcodeKind::Bfind),
2332 "bmsk" => Some(OpcodeKind::Bmsk),
2333 "brev" => Some(OpcodeKind::Brev),
2334 "bra" => Some(OpcodeKind::Bra),
2335 "brkpt" => Some(OpcodeKind::Brkpt),
2336 "brx" => Some(OpcodeKind::Brx),
2337 "call" => Some(OpcodeKind::Call),
2338 "clz" => Some(OpcodeKind::Clz),
2339 "clusterlaunchcontrol" => Some(OpcodeKind::Clusterlaunchcontrol),
2340 "cnot" => Some(OpcodeKind::Cnot),
2341 "copysign" => Some(OpcodeKind::Copysign),
2342 "cos" => Some(OpcodeKind::Cos),
2343 "cp" => Some(OpcodeKind::Cp),
2344 "createpolicy" => Some(OpcodeKind::Createpolicy),
2345 "cvt" => Some(OpcodeKind::Cvt),
2346 "cvta" => Some(OpcodeKind::Cvta),
2347 "div" => Some(OpcodeKind::Div),
2348 "discard" => Some(OpcodeKind::Discard),
2349 "dp2a" => Some(OpcodeKind::Dp2a),
2350 "dp4a" => Some(OpcodeKind::Dp4a),
2351 "elect" => Some(OpcodeKind::Elect),
2352 "ex2" => Some(OpcodeKind::Ex2),
2353 "exit" => Some(OpcodeKind::Exit),
2354 "fence" => Some(OpcodeKind::Fence),
2355 "fma" => Some(OpcodeKind::Fma),
2356 "fns" => Some(OpcodeKind::Fns),
2357 "getctarank" => Some(OpcodeKind::Getctarank),
2358 "griddepcontrol" => Some(OpcodeKind::Griddepcontrol),
2359 "isspacep" => Some(OpcodeKind::Isspacep),
2360 "istypep" => Some(OpcodeKind::Istypep),
2361 "ld" => Some(OpcodeKind::Ld),
2362 "ldmatrix" => Some(OpcodeKind::Ldmatrix),
2363 "ldu" => Some(OpcodeKind::Ldu),
2364 "lg2" => Some(OpcodeKind::Lg2),
2365 "lop3" => Some(OpcodeKind::Lop3),
2366 "mad" => Some(OpcodeKind::Mad),
2367 "mad24" => Some(OpcodeKind::Mad24),
2368 "madc" => Some(OpcodeKind::Madc),
2369 "mapa" => Some(OpcodeKind::Mapa),
2370 "match" => Some(OpcodeKind::Match),
2371 "max" => Some(OpcodeKind::Max),
2372 "mbarrier" => Some(OpcodeKind::Mbarrier),
2373 "membar" => Some(OpcodeKind::Membar),
2374 "min" => Some(OpcodeKind::Min),
2375 "mov" => Some(OpcodeKind::Mov),
2376 "movmatrix" => Some(OpcodeKind::Movmatrix),
2377 "mma" => Some(OpcodeKind::Mma),
2378 "mul" => Some(OpcodeKind::Mul),
2379 "mul24" => Some(OpcodeKind::Mul24),
2380 "multimem" => Some(OpcodeKind::Multimem),
2381 "nanosleep" => Some(OpcodeKind::Nanosleep),
2382 "neg" => Some(OpcodeKind::Neg),
2383 "not" => Some(OpcodeKind::Not),
2384 "or" => Some(OpcodeKind::Or),
2385 "pmevent" => Some(OpcodeKind::Pmevent),
2386 "popc" => Some(OpcodeKind::Popc),
2387 "prefetch" => Some(OpcodeKind::Prefetch),
2388 "prefetchu" => Some(OpcodeKind::Prefetchu),
2389 "prmt" => Some(OpcodeKind::Prmt),
2390 "rcp" => Some(OpcodeKind::Rcp),
2391 "red" => Some(OpcodeKind::Red),
2392 "redux" => Some(OpcodeKind::Redux),
2393 "rem" => Some(OpcodeKind::Rem),
2394 "rsqrt" => Some(OpcodeKind::Rsqrt),
2395 "sad" => Some(OpcodeKind::Sad),
2396 "selp" => Some(OpcodeKind::Selp),
2397 "set" => Some(OpcodeKind::Set),
2398 "setmaxnreg" => Some(OpcodeKind::Setmaxnreg),
2399 "setp" => Some(OpcodeKind::Setp),
2400 "shf" => Some(OpcodeKind::Shf),
2401 "shfl" => Some(OpcodeKind::Shfl),
2402 "shl" => Some(OpcodeKind::Shl),
2403 "shr" => Some(OpcodeKind::Shr),
2404 "sin" => Some(OpcodeKind::Sin),
2405 "slct" => Some(OpcodeKind::Slct),
2406 "sqrt" => Some(OpcodeKind::Sqrt),
2407 "stackrestore" => Some(OpcodeKind::Stackrestore),
2408 "stacksave" => Some(OpcodeKind::Stacksave),
2409 "st" => Some(OpcodeKind::St),
2410 "stmatrix" => Some(OpcodeKind::Stmatrix),
2411 "sub" => Some(OpcodeKind::Sub),
2412 "subc" => Some(OpcodeKind::Subc),
2413 "suq" => Some(OpcodeKind::Suq),
2414 "suld" => Some(OpcodeKind::Suld),
2415 "sured" => Some(OpcodeKind::Sured),
2416 "sust" => Some(OpcodeKind::Sust),
2417 "szext" => Some(OpcodeKind::Szext),
2418 "tanh" => Some(OpcodeKind::Tanh),
2419 "tcgen05" => Some(OpcodeKind::Tcgen05),
2420 "tensormap" => Some(OpcodeKind::Tensormap),
2421 "tex" => Some(OpcodeKind::Tex),
2422 "testp" => Some(OpcodeKind::Testp),
2423 "tld4" => Some(OpcodeKind::Tld4),
2424 "trap" => Some(OpcodeKind::Trap),
2425 "txq" => Some(OpcodeKind::Txq),
2426 "vabsdiff" => Some(OpcodeKind::Vabsdiff),
2427 "vabsdiff2" => Some(OpcodeKind::Vabsdiff2),
2428 "vabsdiff4" => Some(OpcodeKind::Vabsdiff4),
2429 "vadd" => Some(OpcodeKind::Vadd),
2430 "vadd2" => Some(OpcodeKind::Vadd2),
2431 "vadd4" => Some(OpcodeKind::Vadd4),
2432 "vavrg2" => Some(OpcodeKind::Vavrg2),
2433 "vavrg4" => Some(OpcodeKind::Vavrg4),
2434 "vmad" => Some(OpcodeKind::Vmad),
2435 "vmax" => Some(OpcodeKind::Vmax),
2436 "vmax2" => Some(OpcodeKind::Vmax2),
2437 "vmax4" => Some(OpcodeKind::Vmax4),
2438 "vmin" => Some(OpcodeKind::Vmin),
2439 "vmin2" => Some(OpcodeKind::Vmin2),
2440 "vmin4" => Some(OpcodeKind::Vmin4),
2441 "vset" => Some(OpcodeKind::Vset),
2442 "vset2" => Some(OpcodeKind::Vset2),
2443 "vset4" => Some(OpcodeKind::Vset4),
2444 "vshl" => Some(OpcodeKind::Vshl),
2445 "vshr" => Some(OpcodeKind::Vshr),
2446 "vsub" => Some(OpcodeKind::Vsub),
2447 "vsub2" => Some(OpcodeKind::Vsub2),
2448 "vsub4" => Some(OpcodeKind::Vsub4),
2449 "vote" => Some(OpcodeKind::Vote),
2450 "wgmma" => Some(OpcodeKind::Wgmma),
2451 "wmma" => Some(OpcodeKind::Wmma),
2452 "xor" => Some(OpcodeKind::Xor),
2453 "ret" => Some(OpcodeKind::Ret),
2454 _ => None,
2455 }
2456}
2457
2458fn classify_modifier(lower: &str) -> Option<ModifierKind> {
2459 if let Some(space) = parse_address_space_modifier(lower) {
2460 return Some(ModifierKind::AddressSpace(space));
2461 }
2462 if let Some(ty) = parse_type_modifier(lower) {
2463 return Some(ModifierKind::Type(ty));
2464 }
2465 if let Some(cond) = parse_condition_modifier(lower) {
2466 return Some(ModifierKind::Condition(cond));
2467 }
2468 if let Some(rounding) = parse_rounding_modifier(lower) {
2469 return Some(ModifierKind::Rounding(rounding));
2470 }
2471 if let Some(mode) = parse_math_mode_modifier(lower) {
2472 return Some(ModifierKind::MathMode(mode));
2473 }
2474 if let Some(sync) = parse_synchronization_modifier(lower) {
2475 return Some(ModifierKind::Synchronization(sync));
2476 }
2477 if let Some(group) = parse_async_group_modifier(lower) {
2478 return Some(ModifierKind::AsyncGroup(group));
2479 }
2480 if let Some(shuffle) = parse_shuffle_modifier(lower) {
2481 return Some(ModifierKind::Shuffle(shuffle));
2482 }
2483 if let Some(cache) = parse_cache_modifier(lower) {
2484 return Some(ModifierKind::Cache(cache));
2485 }
2486 if let Some(scope) = parse_scope_modifier(lower) {
2487 return Some(ModifierKind::Scope(scope));
2488 }
2489 if let Some(atom) = parse_atomic_modifier(lower) {
2490 return Some(ModifierKind::Atomic(atom));
2491 }
2492 if let Some(call) = parse_call_modifier(lower) {
2493 return Some(ModifierKind::Call(call));
2494 }
2495 if let Some(order) = parse_memory_order_modifier(lower) {
2496 return Some(ModifierKind::MemoryOrder(order));
2497 }
2498
2499 None
2500}
2501
2502fn parse_type_modifier(lower: &str) -> Option<TypeModifier> {
2503 match lower {
2504 "f16" => Some(TypeModifier::F16),
2505 "f32" => Some(TypeModifier::F32),
2506 "f64" => Some(TypeModifier::F64),
2507 "f128" => Some(TypeModifier::F128),
2508 "b8" => Some(TypeModifier::B8),
2509 "b16" => Some(TypeModifier::B16),
2510 "b32" => Some(TypeModifier::B32),
2511 "b64" => Some(TypeModifier::B64),
2512 "s8" => Some(TypeModifier::S8),
2513 "s16" => Some(TypeModifier::S16),
2514 "s32" => Some(TypeModifier::S32),
2515 "s64" => Some(TypeModifier::S64),
2516 "u8" => Some(TypeModifier::U8),
2517 "u16" => Some(TypeModifier::U16),
2518 "u32" => Some(TypeModifier::U32),
2519 "u64" => Some(TypeModifier::U64),
2520 "pred" => Some(TypeModifier::Pred),
2521 _ => None,
2522 }
2523}
2524
2525fn parse_condition_modifier(lower: &str) -> Option<ConditionModifier> {
2526 match lower {
2527 "eq" => Some(ConditionModifier::Eq),
2528 "ne" => Some(ConditionModifier::Ne),
2529 "lt" => Some(ConditionModifier::Lt),
2530 "le" => Some(ConditionModifier::Le),
2531 "gt" => Some(ConditionModifier::Gt),
2532 "ge" => Some(ConditionModifier::Ge),
2533 "lo" => Some(ConditionModifier::Lo),
2534 "hi" => Some(ConditionModifier::Hi),
2535 "ls" => Some(ConditionModifier::Ls),
2536 "hs" => Some(ConditionModifier::Hs),
2537 _ => None,
2538 }
2539}
2540
2541fn parse_address_space_modifier(lower: &str) -> Option<StateSpaceModifier> {
2542 match lower {
2543 "param" => Some(StateSpaceModifier::Param),
2544 "global" => Some(StateSpaceModifier::Global),
2545 "local" => Some(StateSpaceModifier::Local),
2546 "shared" => Some(StateSpaceModifier::Shared),
2547 "const" => Some(StateSpaceModifier::Const),
2548 "generic" => Some(StateSpaceModifier::Generic),
2549 _ => None,
2550 }
2551}
2552
2553fn parse_rounding_modifier(lower: &str) -> Option<RoundingModifier> {
2554 match lower {
2555 "rn" => Some(RoundingModifier::Rn),
2556 "rz" => Some(RoundingModifier::Rz),
2557 "rm" => Some(RoundingModifier::Rm),
2558 "rp" => Some(RoundingModifier::Rp),
2559 _ => None,
2560 }
2561}
2562
2563fn parse_math_mode_modifier(lower: &str) -> Option<MathModeModifier> {
2564 match lower {
2565 "approx" => Some(MathModeModifier::Approx),
2566 "full" => Some(MathModeModifier::Full),
2567 _ => None,
2568 }
2569}
2570
2571fn parse_synchronization_modifier(lower: &str) -> Option<SynchronizationModifier> {
2572 match lower {
2573 "sync" => Some(SynchronizationModifier::Sync),
2574 "async" => Some(SynchronizationModifier::Async),
2575 _ => None,
2576 }
2577}
2578
2579fn parse_async_group_modifier(lower: &str) -> Option<AsyncGroupModifier> {
2580 match lower {
2581 "commit_group" => Some(AsyncGroupModifier::CommitGroup),
2582 "wait_group" => Some(AsyncGroupModifier::WaitGroup),
2583 _ => None,
2584 }
2585}
2586
2587fn parse_shuffle_modifier(lower: &str) -> Option<ShuffleModifier> {
2588 match lower {
2589 "bfly" => Some(ShuffleModifier::Bfly),
2590 "down" => Some(ShuffleModifier::Down),
2591 "up" => Some(ShuffleModifier::Up),
2592 "idx" => Some(ShuffleModifier::Idx),
2593 _ => None,
2594 }
2595}
2596
2597fn parse_cache_modifier(lower: &str) -> Option<CacheModifier> {
2598 match lower {
2599 "nc" => Some(CacheModifier::Nc),
2600 "ca" => Some(CacheModifier::Ca),
2601 "cg" => Some(CacheModifier::Cg),
2602 "cs" => Some(CacheModifier::Cs),
2603 "lu" => Some(CacheModifier::Lu),
2604 _ => None,
2605 }
2606}
2607
2608fn parse_scope_modifier(lower: &str) -> Option<MemoryScopeModifier> {
2609 match lower {
2610 "cta" => Some(MemoryScopeModifier::Cta),
2611 "gl" => Some(MemoryScopeModifier::Gl),
2612 "gpu" => Some(MemoryScopeModifier::Gpu),
2613 "sys" => Some(MemoryScopeModifier::Sys),
2614 _ => None,
2615 }
2616}
2617
2618fn parse_atomic_modifier(lower: &str) -> Option<AtomicOperationModifier> {
2619 match lower {
2620 "cas" => Some(AtomicOperationModifier::Cas),
2621 "add" => Some(AtomicOperationModifier::Add),
2622 "inc" => Some(AtomicOperationModifier::Inc),
2623 "dec" => Some(AtomicOperationModifier::Dec),
2624 "exch" => Some(AtomicOperationModifier::Exch),
2625 "min" => Some(AtomicOperationModifier::Min),
2626 "max" => Some(AtomicOperationModifier::Max),
2627 "and" => Some(AtomicOperationModifier::And),
2628 "or" => Some(AtomicOperationModifier::Or),
2629 "xor" => Some(AtomicOperationModifier::Xor),
2630 _ => None,
2631 }
2632}
2633
2634fn parse_call_modifier(lower: &str) -> Option<CallModifier> {
2635 match lower {
2636 "uni" => Some(CallModifier::Uni),
2637 _ => None,
2638 }
2639}
2640
2641fn parse_vector_width_modifier(lower: &str) -> Option<u32> {
2642 lower
2643 .strip_prefix('v')
2644 .and_then(|value| value.parse::<u32>().ok())
2645}
2646
2647fn parse_memory_order_modifier(lower: &str) -> Option<MemoryOrderModifier> {
2648 match lower {
2649 "relaxed" => Some(MemoryOrderModifier::Relaxed),
2650 "acquire" => Some(MemoryOrderModifier::Acquire),
2651 "release" => Some(MemoryOrderModifier::Release),
2652 "acq_rel" | "acqrel" => Some(MemoryOrderModifier::AcqRel),
2653 "sc" => Some(MemoryOrderModifier::Sc),
2654 _ => None,
2655 }
2656}
2657
2658fn is_numeric_literal(token: &str) -> bool {
2659 if token.is_empty() {
2660 return false;
2661 }
2662
2663 if token.starts_with("0x") || token.starts_with("0X") {
2664 return token.len() > 2 && token[2..].chars().all(|c| c.is_ascii_hexdigit());
2665 }
2666
2667 if token.len() > 2 {
2668 let prefix = &token[..2];
2669 if matches!(prefix, "0d" | "0D" | "0f" | "0F") {
2670 return token[2..].chars().all(|c| c.is_ascii_hexdigit());
2671 }
2672 }
2673
2674 if token.starts_with('-') || token.starts_with('+') {
2675 return is_numeric_literal(&token[1..]);
2676 }
2677
2678 token.parse::<i64>().is_ok() || token.parse::<u64>().is_ok() || token.parse::<f64>().is_ok()
2679}
2680
2681fn is_symbol_token(token: &str) -> bool {
2682 if token.is_empty() {
2683 return false;
2684 }
2685
2686 let mut chars = token.chars();
2687 match chars.next() {
2688 Some(ch) if ch.is_ascii_alphabetic() || ch == '_' || ch == '$' || ch == '.' => {}
2689 _ => return false,
2690 }
2691
2692 chars.all(|ch| {
2693 ch.is_ascii_alphanumeric() || matches!(ch, '_' | '$' | '.' | ':' | '<' | '>' | '?' | '@')
2694 })
2695}
2696
2697fn split_comment(line: &str) -> (String, Option<String>) {
2698 if let Some(pos) = line.find("//") {
2699 let (content, comment) = line.split_at(pos);
2700 (
2701 content.trim_end().to_string(),
2702 Some(comment[2..].trim().to_string()),
2703 )
2704 } else if let Some(pos) = line.find('#') {
2705 let (content, comment) = line.split_at(pos);
2706 (
2707 content.trim_end().to_string(),
2708 Some(comment[1..].trim().to_string()),
2709 )
2710 } else {
2711 (line.to_string(), None)
2712 }
2713}
2714
2715fn strip_comments(line: &str) -> String {
2716 if let Some(pos) = line.find("//") {
2717 line[..pos].trim().to_string()
2718 } else if let Some(pos) = line.find('#') {
2719 line[..pos].trim().to_string()
2720 } else {
2721 line.trim().to_string()
2722 }
2723}
2724
2725fn count_occurrences(line: &str, ch: char) -> usize {
2726 line.chars().filter(|c| *c == ch).count()
2727}
2728
2729fn contains_keyword(line: &str, keyword: &str) -> bool {
2730 line.split_whitespace().any(|token| token == keyword)
2731}
2732
2733fn likely_state_space(line: &str) -> bool {
2734 let trimmed = line.trim_start();
2735 trimmed.starts_with(".tex")
2736 || trimmed.contains(".global")
2737 || trimmed.contains(".const")
2738 || trimmed.contains(".shared")
2739}
2740
2741fn split_initializer(line: &str) -> (&str, Option<&str>) {
2742 if let Some(idx) = line.find('=') {
2743 let (left, right) = line.split_at(idx);
2744 (left.trim_end(), Some(right[1..].trim_start()))
2745 } else {
2746 (line, None)
2747 }
2748}
2749
2750fn parse_target_directive(
2751 rest: &str,
2752 line_number: usize,
2753) -> Result<TargetDirective, PtxParseError> {
2754 if rest.is_empty() {
2755 return Err(PtxParseError::InvalidDirective {
2756 line: line_number,
2757 message: ".target directive expects at least one argument".into(),
2758 });
2759 }
2760
2761 let mut entries = Vec::new();
2762 for entry in rest.split(',') {
2763 let token = entry.trim();
2764 if token.is_empty() {
2765 continue;
2766 }
2767 if !is_valid_target_entry(token) {
2768 return Err(PtxParseError::InvalidDirective {
2769 line: line_number,
2770 message: format!("invalid .target entry '{token}'"),
2771 });
2772 }
2773 entries.push(token.to_string());
2774 }
2775
2776 if entries.is_empty() {
2777 return Err(PtxParseError::InvalidDirective {
2778 line: line_number,
2779 message: "unable to parse arguments for .target directive".into(),
2780 });
2781 }
2782
2783 Ok(TargetDirective {
2784 entries,
2785 raw: rest.to_string(),
2786 })
2787}
2788
2789fn parse_alias(line: &str, line_number: usize) -> Result<FunctionAlias, PtxParseError> {
2790 let trimmed = line.trim();
2791 if !trimmed.ends_with(';') {
2792 return Err(PtxParseError::InvalidDirective {
2793 line: line_number,
2794 message: ".alias directive must end with ';'".into(),
2795 });
2796 }
2797
2798 let body = trimmed
2799 .trim_end_matches(';')
2800 .trim_start_matches(".alias")
2801 .trim();
2802
2803 let mut parts = body
2804 .split(',')
2805 .map(|part| part.trim())
2806 .filter(|part| !part.is_empty());
2807
2808 let alias = parts
2809 .next()
2810 .ok_or_else(|| PtxParseError::InvalidDirective {
2811 line: line_number,
2812 message: "missing alias name in .alias directive".into(),
2813 })?;
2814 let target = parts
2815 .next()
2816 .ok_or_else(|| PtxParseError::InvalidDirective {
2817 line: line_number,
2818 message: "missing aliasee in .alias directive".into(),
2819 })?;
2820
2821 if parts.next().is_some() {
2822 return Err(PtxParseError::InvalidDirective {
2823 line: line_number,
2824 message: "too many operands in .alias directive".into(),
2825 });
2826 }
2827
2828 if alias.is_empty() || target.is_empty() {
2829 return Err(PtxParseError::InvalidDirective {
2830 line: line_number,
2831 message: "invalid operands in .alias directive".into(),
2832 });
2833 }
2834
2835 Ok(FunctionAlias {
2836 alias: alias.to_string(),
2837 target: target.to_string(),
2838 raw: trimmed.to_string(),
2839 })
2840}
2841
2842fn is_valid_target_entry(entry: &str) -> bool {
2843 const BASE_ENTRIES: &[&str] = &[
2844 "sm_120a",
2845 "sm_120f",
2846 "sm_120",
2847 "sm_121a",
2848 "sm_121f",
2849 "sm_121",
2850 "sm_110a",
2851 "sm_110f",
2852 "sm_110",
2853 "sm_100a",
2854 "sm_100f",
2855 "sm_100",
2856 "sm_101a",
2857 "sm_101f",
2858 "sm_101",
2859 "sm_103a",
2860 "sm_103f",
2861 "sm_103",
2862 "sm_90a",
2863 "sm_90",
2864 "sm_80",
2865 "sm_86",
2866 "sm_87",
2867 "sm_88",
2868 "sm_89",
2869 "sm_70",
2870 "sm_72",
2871 "sm_75",
2872 "sm_60",
2873 "sm_61",
2874 "sm_62",
2875 "sm_50",
2876 "sm_52",
2877 "sm_53",
2878 "sm_30",
2879 "sm_32",
2880 "sm_35",
2881 "sm_37",
2882 "sm_20",
2883 "sm_10",
2884 "sm_11",
2885 "sm_12",
2886 "sm_13",
2887 "texmode_unified",
2888 "texmode_independent",
2889 "debug",
2890 "map_f64_to_f32",
2891 ];
2892
2893 BASE_ENTRIES.contains(&entry)
2894}
2895
2896#[derive(Debug)]
2897struct ParsedStateVariable {
2898 visibility: Option<GlobalVisibility>,
2899 linkages: Vec<GlobalLinkage>,
2900 address_space: Option<GlobalAddressSpace>,
2901 mutability: Option<GlobalMutability>,
2902 alignment: Option<u32>,
2903 ty: Option<ScalarType>,
2904 qualifiers: Vec<VariableQualifier>,
2905 name: String,
2906 array: Option<ArraySpecifier>,
2907 initializer: Option<GlobalInitializer>,
2908 raw: String,
2909}
2910
2911impl ParsedStateVariable {
2912 fn into_variable(mut self, space: GlobalAddressSpace) -> VariableDirective {
2913 self.address_space = Some(space);
2914 VariableDirective {
2915 visibility: self.visibility,
2916 linkages: self.linkages,
2917 address_space: self.address_space,
2918 mutability: self.mutability,
2919 alignment: self.alignment,
2920 ty: self.ty,
2921 qualifiers: self.qualifiers,
2922 name: self.name,
2923 array: self.array,
2924 initializer: self.initializer,
2925 raw: self.raw,
2926 }
2927 }
2928}
2929
2930fn parse_register_declaration(
2931 keyword: &str,
2932 directive_body: &str,
2933 comment: Option<String>,
2934 raw: String,
2935 line_number: usize,
2936) -> Result<RegisterDeclaration, PtxParseError> {
2937 let rest = directive_body
2938 .strip_prefix(keyword)
2939 .unwrap_or(directive_body)
2940 .trim_start();
2941
2942 if rest.is_empty() {
2943 return Err(PtxParseError::InvalidDirective {
2944 line: line_number,
2945 message: format!("missing register type in '{keyword}' directive"),
2946 });
2947 }
2948
2949 let (type_token, registers_part) =
2950 if let Some((idx, _)) = rest.char_indices().find(|(_, ch)| ch.is_whitespace()) {
2951 let (ty, remainder) = rest.split_at(idx);
2952 (ty, remainder.trim_start())
2953 } else {
2954 return Err(PtxParseError::InvalidDirective {
2955 line: line_number,
2956 message: format!("missing register list in '{keyword}' directive"),
2957 });
2958 };
2959
2960 if type_token.is_empty() {
2961 return Err(PtxParseError::InvalidDirective {
2962 line: line_number,
2963 message: format!("missing register type in '{keyword}' directive"),
2964 });
2965 }
2966
2967 if registers_part.is_empty() {
2968 return Err(PtxParseError::InvalidDirective {
2969 line: line_number,
2970 message: format!("missing registers in '{keyword}' directive"),
2971 });
2972 }
2973
2974 let ty = RegisterType {
2975 scalar: parse_scalar_type(type_token),
2976 raw: type_token.to_string(),
2977 };
2978
2979 let mut registers = Vec::new();
2980 for spec in registers_part.split(',') {
2981 let trimmed = spec.trim();
2982 if trimmed.is_empty() {
2983 continue;
2984 }
2985 registers.push(parse_register_specifier(trimmed, line_number)?);
2986 }
2987
2988 if registers.is_empty() {
2989 return Err(PtxParseError::InvalidDirective {
2990 line: line_number,
2991 message: format!("no registers listed in '{keyword}' directive"),
2992 });
2993 }
2994
2995 Ok(RegisterDeclaration {
2996 keyword: keyword.to_string(),
2997 ty,
2998 registers,
2999 comment,
3000 raw,
3001 })
3002}
3003
3004fn parse_register_specifier(
3005 token: &str,
3006 line_number: usize,
3007) -> Result<RegisterSpecifier, PtxParseError> {
3008 if let Some(start) = token.find('<') {
3009 let end = token
3010 .rfind('>')
3011 .ok_or_else(|| PtxParseError::InvalidDirective {
3012 line: line_number,
3013 message: format!("unterminated register range '{token}'"),
3014 })?;
3015
3016 if end <= start + 1 {
3017 return Err(PtxParseError::InvalidDirective {
3018 line: line_number,
3019 message: format!("empty register range '{token}'"),
3020 });
3021 }
3022
3023 let prefix = token[..start].trim();
3024 if prefix.is_empty() {
3025 return Err(PtxParseError::InvalidDirective {
3026 line: line_number,
3027 message: format!("missing prefix in register range '{token}'"),
3028 });
3029 }
3030
3031 let count_str = token[start + 1..end].trim();
3032 if count_str.is_empty() {
3033 return Err(PtxParseError::InvalidDirective {
3034 line: line_number,
3035 message: format!("missing count in register range '{token}'"),
3036 });
3037 }
3038
3039 let count = count_str
3040 .parse::<u32>()
3041 .map_err(|_| PtxParseError::InvalidDirective {
3042 line: line_number,
3043 message: format!("invalid register count '{count_str}' in '{token}'"),
3044 })?;
3045
3046 return Ok(RegisterSpecifier::Range {
3047 prefix: prefix.to_string(),
3048 count,
3049 });
3050 }
3051
3052 Ok(RegisterSpecifier::Named(token.to_string()))
3053}
3054
3055fn split_name_token(
3056 token: &str,
3057 line_number: usize,
3058) -> Result<(String, Option<ArraySpecifier>), PtxParseError> {
3059 if let Some(start) = token.find('[') {
3060 if !token.ends_with(']') {
3061 return Err(PtxParseError::InvalidGlobal {
3062 line: line_number,
3063 message: format!("unterminated array specifier in global name '{token}'"),
3064 });
3065 }
3066
3067 let name = token[..start].to_string();
3068 let spec = parse_array_specifier(&token[start..], line_number)?;
3069 Ok((name, Some(spec)))
3070 } else {
3071 Ok((token.to_string(), None))
3072 }
3073}
3074
3075fn parse_array_specifier(spec: &str, line_number: usize) -> Result<ArraySpecifier, PtxParseError> {
3076 if spec.is_empty() || !spec.starts_with('[') {
3077 return Err(PtxParseError::InvalidGlobal {
3078 line: line_number,
3079 message: format!("invalid array specifier '{spec}'"),
3080 });
3081 }
3082
3083 let mut dimensions = Vec::new();
3084 let mut idx = 0;
3085 while idx < spec.len() {
3086 let remaining = &spec[idx..];
3087 if !remaining.starts_with('[') {
3088 return Err(PtxParseError::InvalidGlobal {
3089 line: line_number,
3090 message: format!("malformed array specifier '{spec}'"),
3091 });
3092 }
3093
3094 let close_offset = remaining
3095 .find(']')
3096 .ok_or_else(|| PtxParseError::InvalidGlobal {
3097 line: line_number,
3098 message: format!("unterminated array specifier '{spec}'"),
3099 })?;
3100 let close_idx = idx + close_offset;
3101 let inner = spec[idx + 1..close_idx].trim();
3102 if inner.is_empty() {
3103 dimensions.push(None);
3104 } else {
3105 let bound = inner
3106 .parse::<u64>()
3107 .map_err(|_| PtxParseError::InvalidGlobal {
3108 line: line_number,
3109 message: format!("invalid array bound '{inner}'"),
3110 })?;
3111 dimensions.push(Some(bound));
3112 }
3113 idx = close_idx + 1;
3114 }
3115
3116 if dimensions.is_empty() {
3117 return Err(PtxParseError::InvalidGlobal {
3118 line: line_number,
3119 message: format!("empty array specifier '{spec}'"),
3120 });
3121 }
3122
3123 Ok(ArraySpecifier { dimensions })
3124}
3125
3126fn parse_global_visibility(token: &str) -> Option<GlobalVisibility> {
3127 match token {
3128 ".visible" => Some(GlobalVisibility::Visible),
3129 ".hidden" => Some(GlobalVisibility::Hidden),
3130 _ => None,
3131 }
3132}
3133
3134fn parse_global_linkage(token: &str) -> Option<GlobalLinkage> {
3135 match token {
3136 ".extern" => Some(GlobalLinkage::Extern),
3137 ".weak" => Some(GlobalLinkage::Weak),
3138 ".weakextern" => Some(GlobalLinkage::WeakExtern),
3139 _ => None,
3140 }
3141}
3142
3143fn parse_global_address_space(token: &str) -> Option<GlobalAddressSpace> {
3144 match token {
3145 ".global" => Some(GlobalAddressSpace::Global),
3146 ".const" => Some(GlobalAddressSpace::Const),
3147 ".shared" => Some(GlobalAddressSpace::Shared),
3148 ".local" => Some(GlobalAddressSpace::Local),
3149 _ => None,
3150 }
3151}
3152
3153fn parse_global_mutability(token: &str) -> Option<GlobalMutability> {
3154 match token {
3155 ".const" => Some(GlobalMutability::Const),
3156 _ => None,
3157 }
3158}
3159
3160fn parse_scalar_type(token: &str) -> Option<ScalarType> {
3161 if !token.starts_with('.') {
3162 return None;
3163 }
3164
3165 match token[1..].to_ascii_lowercase().as_str() {
3166 "b8" => Some(ScalarType::B8),
3167 "b16" => Some(ScalarType::B16),
3168 "b32" => Some(ScalarType::B32),
3169 "b64" => Some(ScalarType::B64),
3170 "s8" => Some(ScalarType::S8),
3171 "s16" => Some(ScalarType::S16),
3172 "s32" => Some(ScalarType::S32),
3173 "s64" => Some(ScalarType::S64),
3174 "u8" => Some(ScalarType::U8),
3175 "u16" => Some(ScalarType::U16),
3176 "u32" => Some(ScalarType::U32),
3177 "u64" => Some(ScalarType::U64),
3178 "f16" => Some(ScalarType::F16),
3179 "f32" => Some(ScalarType::F32),
3180 "f64" => Some(ScalarType::F64),
3181 "pred" => Some(ScalarType::Pred),
3182 "texref" => Some(ScalarType::TexRef),
3183 "samplerref" => Some(ScalarType::SamplerRef),
3184 "surfref" => Some(ScalarType::SurfRef),
3185 _ => None,
3186 }
3187}
3188
3189enum VariableQualifierContext {
3190 Tex,
3191 StateSpace,
3192}
3193
3194fn variable_qualifier_from_token(
3195 token: &str,
3196 line_number: usize,
3197 context: VariableQualifierContext,
3198) -> Result<VariableQualifier, PtxParseError> {
3199 if let Some(rest) = token.strip_prefix(".v") {
3200 if let Ok(width) = rest.parse::<u32>() {
3201 return Ok(VariableQualifier::Vector(width));
3202 }
3203 }
3204
3205 if matches!(context, VariableQualifierContext::Tex) && token.eq_ignore_ascii_case(".sampler") {
3206 return Ok(VariableQualifier::Sampler);
3207 }
3208
3209 let message = format!("unrecognised qualifier '{}'", token);
3210 match context {
3211 VariableQualifierContext::Tex => Err(PtxParseError::InvalidDirective {
3212 line: line_number,
3213 message,
3214 }),
3215 VariableQualifierContext::StateSpace => Err(PtxParseError::InvalidGlobal {
3216 line: line_number,
3217 message,
3218 }),
3219 }
3220}
3221
3222fn parse_global_initializer(
3223 raw: &str,
3224 line_number: usize,
3225) -> Result<GlobalInitializer, PtxParseError> {
3226 let trimmed = raw.trim();
3227 if trimmed.is_empty() {
3228 return Err(PtxParseError::InvalidGlobal {
3229 line: line_number,
3230 message: "empty global initializer".into(),
3231 });
3232 }
3233
3234 if trimmed.starts_with('{') {
3235 if !trimmed.ends_with('}') {
3236 return Err(PtxParseError::InvalidGlobal {
3237 line: line_number,
3238 message: "unterminated aggregate initializer".into(),
3239 });
3240 }
3241 let inner = &trimmed[1..trimmed.len() - 1];
3242 let mut values = Vec::new();
3243 for segment in split_top_level_commas(inner) {
3244 let token = segment.trim();
3245 if token.is_empty() {
3246 continue;
3247 }
3248 values.push(parse_global_initializer(token, line_number)?);
3249 }
3250 return Ok(GlobalInitializer::Aggregate(values));
3251 }
3252
3253 let value = parse_initializer_value(trimmed, line_number)?;
3254 Ok(GlobalInitializer::Scalar(value))
3255}
3256
3257fn split_top_level_commas(input: &str) -> Vec<&str> {
3258 let mut parts = Vec::new();
3259 let mut depth = 0i32;
3260 let mut start = 0usize;
3261 for (idx, ch) in input.char_indices() {
3262 match ch {
3263 '{' => depth += 1,
3264 '}' => {
3265 if depth > 0 {
3266 depth -= 1;
3267 }
3268 }
3269 ',' if depth == 0 => {
3270 parts.push(&input[start..idx]);
3271 start = idx + 1;
3272 }
3273 _ => {}
3274 }
3275 }
3276 if start <= input.len() {
3277 parts.push(&input[start..]);
3278 }
3279 parts
3280}
3281
3282fn parse_initializer_value(
3283 token: &str,
3284 line_number: usize,
3285) -> Result<InitializerValue, PtxParseError> {
3286 if token.is_empty() {
3287 return Err(PtxParseError::InvalidGlobal {
3288 line: line_number,
3289 message: "empty initializer token".into(),
3290 });
3291 }
3292
3293 if token.starts_with('"') {
3294 if !token.ends_with('"') || token.len() < 2 {
3295 return Err(PtxParseError::InvalidGlobal {
3296 line: line_number,
3297 message: "unterminated string literal in initializer".into(),
3298 });
3299 }
3300 let literal = parse_string_literal(token, line_number)?;
3301 return Ok(InitializerValue::StringLiteral(literal));
3302 }
3303
3304 if let Some(numeric) = try_parse_numeric_literal(token) {
3305 return Ok(InitializerValue::Numeric(numeric));
3306 }
3307
3308 if is_symbol_token(token) {
3309 return Ok(InitializerValue::Symbol(token.to_string()));
3310 }
3311
3312 Ok(InitializerValue::Symbol(token.to_string()))
3313}
3314
3315fn parse_string_literal(token: &str, line_number: usize) -> Result<String, PtxParseError> {
3316 let inner = &token[1..token.len() - 1];
3317 let mut result = String::with_capacity(inner.len());
3318 let mut chars = inner.chars();
3319 while let Some(ch) = chars.next() {
3320 if ch == '\\' {
3321 let Some(escaped) = chars.next() else {
3322 return Err(PtxParseError::InvalidGlobal {
3323 line: line_number,
3324 message: "incomplete escape sequence in string literal".into(),
3325 });
3326 };
3327 match escaped {
3328 'n' => result.push('\n'),
3329 'r' => result.push('\r'),
3330 't' => result.push('\t'),
3331 '\\' => result.push('\\'),
3332 '"' => result.push('"'),
3333 other => {
3334 result.push(other);
3335 }
3336 }
3337 } else {
3338 result.push(ch);
3339 }
3340 }
3341 Ok(result)
3342}
3343
3344fn unquote_string(token: &str, line_number: usize) -> Result<String, PtxParseError> {
3345 if token.starts_with('"') && token.ends_with('"') && token.len() >= 2 {
3346 parse_string_literal(token, line_number)
3347 } else {
3348 Ok(token.to_string())
3349 }
3350}
3351
3352fn try_parse_numeric_literal(token: &str) -> Option<NumericLiteral> {
3353 if let Some(hex) = token
3354 .strip_prefix("0d")
3355 .or_else(|| token.strip_prefix("0D"))
3356 {
3357 if let Ok(bits) = u64::from_str_radix(hex, 16) {
3358 return Some(NumericLiteral::Float64(bits));
3359 }
3360 }
3361
3362 if let Some(hex) = token
3363 .strip_prefix("0f")
3364 .or_else(|| token.strip_prefix("0F"))
3365 {
3366 if let Ok(bits) = u32::from_str_radix(hex, 16) {
3367 return Some(NumericLiteral::Float32(bits));
3368 }
3369 }
3370
3371 if let Some(hex) = token
3372 .strip_prefix("0x")
3373 .or_else(|| token.strip_prefix("0X"))
3374 {
3375 if let Ok(value) = u64::from_str_radix(hex, 16) {
3376 return Some(NumericLiteral::Unsigned(value));
3377 }
3378 }
3379
3380 if let Some(hex) = token
3381 .strip_prefix("-0x")
3382 .or_else(|| token.strip_prefix("-0X"))
3383 {
3384 if let Ok(value) = i64::from_str_radix(hex, 16) {
3385 return Some(NumericLiteral::Signed(-value));
3386 }
3387 }
3388
3389 if let Ok(value) = token.parse::<i64>() {
3390 return Some(NumericLiteral::Signed(value));
3391 }
3392
3393 if let Ok(value) = token.parse::<u64>() {
3394 return Some(NumericLiteral::Unsigned(value));
3395 }
3396
3397 if let Ok(value) = token.parse::<f64>() {
3398 return Some(NumericLiteral::Float64(value.to_bits()));
3399 }
3400
3401 if let Ok(value) = token.parse::<f32>() {
3402 return Some(NumericLiteral::Float32(value.to_bits()));
3403 }
3404
3405 None
3406}