Skip to main content

ptx_parser/unparser/
function.rs

1use crate::unparser::common::push_register;
2use crate::{
3    lexer::PtxToken,
4    r#type::{function::*, variable::ParameterDirective},
5    unparser::*,
6};
7
8fn push_register_components(tokens: &mut Vec<PtxToken>, name: &str) {
9    if let Some(stripped) = name.strip_prefix('%') {
10        let mut parts = stripped.split('.');
11        if let Some(first) = parts.next() {
12            let register_name = format!("%{first}");
13            push_register(tokens, &register_name);
14        }
15        for part in parts {
16            if part.is_empty() {
17                continue;
18            }
19            push_directive(tokens, part);
20        }
21    } else {
22        push_identifier(tokens, name);
23    }
24}
25
26fn unparse_param(tokens: &mut Vec<PtxToken>, param: &ParameterDirective, spaced: bool) {
27    match param {
28        ParameterDirective::Parameter {
29            align,
30            ty,
31            ptr,
32            space,
33            name,
34            array,
35            ..
36        } => {
37            push_directive(tokens, "param");
38            push_space(tokens, spaced);
39            // Alignment must come before type in PTX syntax
40            if let Some(value) = align {
41                push_directive(tokens, "align");
42                push_space(tokens, spaced);
43                push_decimal(tokens, *value);
44                push_space(tokens, spaced);
45            }
46            ty.unparse_tokens_mode(tokens, spaced);
47            if *ptr {
48                push_directive(tokens, "ptr");
49            }
50            if let Some(address_space) = space {
51                address_space.unparse_tokens_mode(tokens, spaced);
52            }
53            push_space(tokens, spaced);
54            push_identifier(tokens, &name.val);
55            for extent in array {
56                tokens.push(PtxToken::LBracket);
57                if let Some(value) = extent {
58                    push_decimal(tokens, *value);
59                }
60                tokens.push(PtxToken::RBracket);
61            }
62        }
63        ParameterDirective::Register { ty, name, .. } => {
64            push_directive(tokens, "reg");
65            push_space(tokens, spaced);
66            ty.unparse_tokens_mode(tokens, spaced);
67            push_space(tokens, spaced);
68            push_register_components(tokens, &name.val);
69        }
70    }
71}
72
73fn unparse_param_list(tokens: &mut Vec<PtxToken>, params: &[ParameterDirective], spaced: bool) {
74    for (idx, param) in params.iter().enumerate() {
75        if idx > 0 {
76            tokens.push(PtxToken::Comma);
77            push_space(tokens, spaced);
78        }
79        unparse_param(tokens, param, spaced);
80    }
81}
82
83fn unparse_section_line(
84    tokens: &mut Vec<PtxToken>,
85    line: &StatementSectionDirectiveLine,
86    spaced: bool,
87) {
88    match line {
89        StatementSectionDirectiveLine::B8 { values, .. } => {
90            push_directive(tokens, "b8");
91            for (idx, value) in values.iter().enumerate() {
92                if idx > 0 {
93                    tokens.push(PtxToken::Comma);
94                    push_space(tokens, spaced);
95                }
96                push_space(tokens, spaced);
97                push_signed_decimal_i64(tokens, *value as i64);
98            }
99            push_newline(tokens, spaced);
100        }
101        StatementSectionDirectiveLine::B16 { values, .. } => {
102            push_directive(tokens, "b16");
103            for (idx, value) in values.iter().enumerate() {
104                if idx > 0 {
105                    tokens.push(PtxToken::Comma);
106                    push_space(tokens, spaced);
107                }
108                push_space(tokens, spaced);
109                push_signed_decimal_i64(tokens, *value as i64);
110            }
111            push_newline(tokens, spaced);
112        }
113        StatementSectionDirectiveLine::B32Immediate { values, .. } => {
114            push_directive(tokens, "b32");
115            for (idx, value) in values.iter().enumerate() {
116                if idx > 0 {
117                    tokens.push(PtxToken::Comma);
118                    push_space(tokens, spaced);
119                }
120                push_space(tokens, spaced);
121                push_signed_decimal_i64(tokens, *value);
122            }
123            push_newline(tokens, spaced);
124        }
125        StatementSectionDirectiveLine::B64Immediate { values, .. } => {
126            push_directive(tokens, "b64");
127            for (idx, value) in values.iter().enumerate() {
128                if idx > 0 {
129                    tokens.push(PtxToken::Comma);
130                    push_space(tokens, spaced);
131                }
132                push_space(tokens, spaced);
133                push_signed_decimal_i128(tokens, *value);
134            }
135            push_newline(tokens, spaced);
136        }
137        StatementSectionDirectiveLine::B32Label { labels, .. } => {
138            push_directive(tokens, "b32");
139            push_space(tokens, spaced);
140            push_identifier(tokens, &labels.val);
141            push_newline(tokens, spaced);
142        }
143        StatementSectionDirectiveLine::B64Label { labels, .. } => {
144            push_directive(tokens, "b64");
145            push_space(tokens, spaced);
146            push_identifier(tokens, &labels.val);
147            push_newline(tokens, spaced);
148        }
149        StatementSectionDirectiveLine::B32LabelPlusImm { entries, .. } => {
150            push_directive(tokens, "b32");
151            let (label, offset) = entries;
152            push_space(tokens, spaced);
153            push_identifier(tokens, &label.val);
154            if *offset >= 0 {
155                tokens.push(PtxToken::Plus);
156                push_decimal(tokens, *offset);
157            } else {
158                tokens.push(PtxToken::Minus);
159                let magnitude = (*offset as i128).abs();
160                push_decimal(tokens, magnitude);
161            }
162            push_newline(tokens, spaced);
163        }
164        StatementSectionDirectiveLine::B64LabelPlusImm { entries, .. } => {
165            push_directive(tokens, "b64");
166            let (label, offset) = entries;
167            push_space(tokens, spaced);
168            push_identifier(tokens, &label.val);
169            if *offset >= 0 {
170                tokens.push(PtxToken::Plus);
171                push_decimal(tokens, *offset);
172            } else {
173                tokens.push(PtxToken::Minus);
174                let magnitude = (*offset as i128).abs();
175                push_decimal(tokens, magnitude);
176            }
177            push_newline(tokens, spaced);
178        }
179        StatementSectionDirectiveLine::B32LabelDiff { entries, .. } => {
180            push_directive(tokens, "b32");
181            let (left, right) = entries;
182            push_space(tokens, spaced);
183            push_identifier(tokens, &left.val);
184            tokens.push(PtxToken::Minus);
185            push_space(tokens, spaced);
186            push_identifier(tokens, &right.val);
187            push_newline(tokens, spaced);
188        }
189        StatementSectionDirectiveLine::B64LabelDiff { entries, .. } => {
190            push_directive(tokens, "b64");
191            let (left, right) = entries;
192            push_space(tokens, spaced);
193            push_identifier(tokens, &left.val);
194            tokens.push(PtxToken::Minus);
195            push_space(tokens, spaced);
196            push_identifier(tokens, &right.val);
197            push_newline(tokens, spaced);
198        }
199    }
200}
201
202fn push_signed_decimal_i64(tokens: &mut Vec<PtxToken>, value: i64) {
203    if value < 0 {
204        tokens.push(PtxToken::Minus);
205        push_decimal(tokens, (-value) as i128);
206    } else {
207        push_decimal(tokens, value);
208    }
209}
210
211fn push_signed_decimal_i128(tokens: &mut Vec<PtxToken>, value: i128) {
212    if value < 0 {
213        tokens.push(PtxToken::Minus);
214        push_decimal(tokens, -value);
215    } else {
216        push_decimal(tokens, value);
217    }
218}
219
220impl PtxUnparser for RegisterDirective {
221    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
222        self.unparse_tokens_mode(tokens, false);
223    }
224
225    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
226        push_directive(tokens, "reg");
227        push_space(tokens, spaced);
228        if let Some(vector) = self.vector {
229            push_directive(
230                tokens,
231                match vector {
232                    RegisterVectorWidth::V2 => "v2",
233                    RegisterVectorWidth::V4 => "v4",
234                },
235            );
236            push_space(tokens, spaced);
237        }
238        self.ty.unparse_tokens_mode(tokens, spaced);
239        for (idx, target) in self.registers.iter().enumerate() {
240            if idx > 0 {
241                tokens.push(PtxToken::Comma);
242                push_space(tokens, spaced);
243            } else {
244                push_space(tokens, spaced);
245            }
246            push_register_components(tokens, &target.name.val);
247            if let Some(range) = target.range {
248                tokens.push(PtxToken::LAngle);
249                push_decimal(tokens, range);
250                tokens.push(PtxToken::RAngle);
251            }
252        }
253        tokens.push(PtxToken::Semicolon);
254        push_newline(tokens, spaced);
255    }
256}
257
258impl PtxUnparser for StatementDirective {
259    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
260        self.unparse_tokens_mode(tokens, false);
261    }
262
263    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
264        match self {
265            StatementDirective::Reg {
266                directive: register,
267                ..
268            } => register.unparse_tokens_mode(tokens, spaced),
269            StatementDirective::Local {
270                directive: variable,
271                ..
272            } => {
273                push_directive(tokens, "local");
274                push_space(tokens, spaced);
275                variable.unparse_tokens_mode(tokens, spaced);
276            }
277            StatementDirective::Param {
278                directive: variable,
279                ..
280            } => {
281                push_directive(tokens, "param");
282                push_space(tokens, spaced);
283                variable.unparse_tokens_mode(tokens, spaced);
284            }
285            StatementDirective::Shared {
286                directive: variable,
287                ..
288            } => {
289                push_directive(tokens, "shared");
290                push_space(tokens, spaced);
291                variable.unparse_tokens_mode(tokens, spaced);
292            }
293            StatementDirective::Pragma {
294                directive: pragma, ..
295            } => {
296                push_directive(tokens, "pragma");
297                push_space(tokens, spaced);
298                let text = match &pragma.kind {
299                    PragmaDirectiveKind::Nounroll => "nounroll".to_string(),
300                    PragmaDirectiveKind::EnableSmemSpilling => "enable_smem_spilling".to_string(),
301                    PragmaDirectiveKind::UsedBytesMask { mask } => {
302                        format!("used_bytes_mask {}", mask)
303                    }
304                    PragmaDirectiveKind::Frequency { value } => {
305                        format!("frequency {}", value)
306                    }
307                    PragmaDirectiveKind::Raw(text) => text.clone(),
308                };
309                tokens.push(PtxToken::StringLiteral(text));
310                tokens.push(PtxToken::Semicolon);
311                push_newline(tokens, spaced);
312            }
313            StatementDirective::BranchTargets { directive, .. } => {
314                push_directive(tokens, "branchtargets");
315                push_space(tokens, spaced);
316                for (idx, label) in directive.labels.iter().enumerate() {
317                    if idx > 0 {
318                        tokens.push(PtxToken::Comma);
319                        push_space(tokens, spaced);
320                    }
321                    push_token_from_str(tokens, &label.val);
322                }
323                tokens.push(PtxToken::Semicolon);
324                push_newline(tokens, spaced);
325            }
326            StatementDirective::CallTargets { directive, .. } => {
327                push_directive(tokens, "calltargets");
328                push_space(tokens, spaced);
329                for (idx, target) in directive.targets.iter().enumerate() {
330                    if idx > 0 {
331                        tokens.push(PtxToken::Comma);
332                        push_space(tokens, spaced);
333                    }
334                    push_token_from_str(tokens, &target.val);
335                }
336                tokens.push(PtxToken::Semicolon);
337                push_newline(tokens, spaced);
338            }
339            StatementDirective::Loc { directive: loc, .. } => {
340                push_directive(tokens, "loc");
341                push_space(tokens, spaced);
342                push_decimal(tokens, loc.file_index);
343                push_space(tokens, spaced);
344                push_decimal(tokens, loc.line);
345                push_space(tokens, spaced);
346                push_decimal(tokens, loc.column);
347                if let Some(function) = &loc.function {
348                    tokens.push(PtxToken::Comma);
349                    push_space(tokens, spaced);
350                    push_identifier(tokens, "function_name");
351                    push_space(tokens, spaced);
352                    function.label.unparse_tokens_mode(tokens, spaced);
353                    if let Some(offset) = function.label_offset {
354                        tokens.push(PtxToken::Plus);
355                        push_decimal(tokens, offset);
356                    }
357                    tokens.push(PtxToken::Comma);
358                    push_space(tokens, spaced);
359                    push_identifier(tokens, "inlined_at");
360                    push_space(tokens, spaced);
361                    push_decimal(tokens, function.inlined_at.file_index);
362                    push_space(tokens, spaced);
363                    push_decimal(tokens, function.inlined_at.line);
364                    push_space(tokens, spaced);
365                    push_decimal(tokens, function.inlined_at.column);
366                }
367                push_newline(tokens, spaced);
368            }
369            StatementDirective::Dwarf {
370                directive: dwarf, ..
371            } => {
372                dwarf.unparse_tokens_mode(tokens, spaced);
373                push_newline(tokens, spaced);
374            }
375            StatementDirective::Section {
376                directive: section, ..
377            } => {
378                section.unparse_tokens_mode(tokens, spaced);
379            }
380            StatementDirective::CallPrototype { directive, .. } => {
381                push_directive(tokens, "callprototype");
382                push_space(tokens, spaced);
383                if let Some(ret) = &directive.return_param {
384                    unparse_param(tokens, ret, spaced);
385                } else {
386                    push_identifier(tokens, "_");
387                }
388                tokens.push(PtxToken::LParen);
389                unparse_param_list(tokens, &directive.params, spaced);
390                tokens.push(PtxToken::RParen);
391                if directive.noreturn {
392                    push_space(tokens, spaced);
393                    push_directive(tokens, "noreturn");
394                }
395                if let Some(value) = directive.abi_preserve {
396                    push_space(tokens, spaced);
397                    push_directive(tokens, "abi_preserve");
398                    push_space(tokens, spaced);
399                    push_decimal(tokens, value);
400                }
401                if let Some(value) = directive.abi_preserve_control {
402                    push_space(tokens, spaced);
403                    push_directive(tokens, "abi_preserve_control");
404                    push_space(tokens, spaced);
405                    push_decimal(tokens, value);
406                }
407                tokens.push(PtxToken::Semicolon);
408                push_newline(tokens, spaced);
409            }
410        }
411    }
412}
413
414impl PtxUnparser for SectionDirective {
415    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
416        self.unparse_tokens_mode(tokens, false);
417    }
418
419    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
420        push_directive(tokens, "section");
421        push_space(tokens, spaced);
422        push_token_from_str(tokens, &self.name);
423        push_space(tokens, spaced);
424        tokens.push(PtxToken::LBrace);
425        push_newline(tokens, spaced);
426        for entry in &self.entries {
427            match entry {
428                SectionEntry::Label { label, .. } => {
429                    push_identifier(tokens, &label.val);
430                    tokens.push(PtxToken::Colon);
431                    push_newline(tokens, spaced);
432                }
433                SectionEntry::Directive(line) => {
434                    unparse_section_line(tokens, line, spaced);
435                }
436            }
437        }
438        tokens.push(PtxToken::RBrace);
439        push_newline(tokens, spaced);
440    }
441}
442
443impl PtxUnparser for FunctionStatement {
444    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
445        self.unparse_tokens_mode(tokens, false);
446    }
447
448    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
449        match self {
450            FunctionStatement::Label { label, .. } => {
451                push_identifier(tokens, &label.val);
452                tokens.push(PtxToken::Colon);
453                push_newline(tokens, spaced);
454            }
455            FunctionStatement::Instruction { instruction, .. } => {
456                instruction.unparse_tokens_mode(tokens, spaced)
457            }
458            FunctionStatement::Directive { directive, .. } => {
459                directive.unparse_tokens_mode(tokens, spaced)
460            }
461            FunctionStatement::Block {
462                statements: block, ..
463            } => {
464                tokens.push(PtxToken::LBrace);
465                for statement in block {
466                    statement.unparse_tokens_mode(tokens, spaced);
467                }
468                tokens.push(PtxToken::RBrace);
469                push_newline(tokens, spaced);
470            }
471        }
472    }
473}
474
475impl PtxUnparser for FunctionBody {
476    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
477        self.unparse_tokens_mode(tokens, false);
478    }
479
480    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
481        tokens.push(PtxToken::LBrace);
482        push_newline(tokens, spaced);
483        for statement in &self.statements {
484            statement.unparse_tokens_mode(tokens, spaced);
485        }
486        tokens.push(PtxToken::RBrace);
487        push_newline(tokens, spaced);
488    }
489}
490
491impl PtxUnparser for FunctionDim {
492    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
493        self.unparse_tokens_mode(tokens, false);
494    }
495
496    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
497        match self {
498            FunctionDim::X { x, .. } => {
499                push_decimal(tokens, *x);
500            }
501            FunctionDim::XY { x, y, .. } => {
502                push_decimal(tokens, *x);
503                tokens.push(PtxToken::Comma);
504                push_space(tokens, spaced);
505                push_decimal(tokens, *y);
506            }
507            FunctionDim::XYZ { x, y, z, .. } => {
508                push_decimal(tokens, *x);
509                tokens.push(PtxToken::Comma);
510                push_space(tokens, spaced);
511                push_decimal(tokens, *y);
512                tokens.push(PtxToken::Comma);
513                push_space(tokens, spaced);
514                push_decimal(tokens, *z);
515            }
516        }
517    }
518}
519
520impl PtxUnparser for EntryFunctionHeaderDirective {
521    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
522        self.unparse_tokens_mode(tokens, false);
523    }
524
525    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
526        match self {
527            EntryFunctionHeaderDirective::MaxNReg { value, .. } => {
528                push_directive(tokens, "maxnreg");
529                push_space(tokens, spaced);
530                push_decimal(tokens, *value);
531            }
532            EntryFunctionHeaderDirective::MaxNTid { dim, .. } => {
533                push_directive(tokens, "maxntid");
534                push_space(tokens, spaced);
535                dim.unparse_tokens_mode(tokens, spaced);
536            }
537            EntryFunctionHeaderDirective::ReqNTid { dim, .. } => {
538                push_directive(tokens, "reqntid");
539                push_space(tokens, spaced);
540                dim.unparse_tokens_mode(tokens, spaced);
541            }
542            EntryFunctionHeaderDirective::MinNCtaPerSm { value, .. } => {
543                push_directive(tokens, "minnctapersm");
544                push_space(tokens, spaced);
545                push_decimal(tokens, *value);
546            }
547            EntryFunctionHeaderDirective::MaxNCtaPerSm { value, .. } => {
548                push_directive(tokens, "maxnctapersm");
549                push_space(tokens, spaced);
550                push_decimal(tokens, *value);
551            }
552            EntryFunctionHeaderDirective::Pragma {
553                args: arguments, ..
554            } => {
555                push_directive(tokens, "pragma");
556                push_space(tokens, spaced);
557                for argument in arguments {
558                    tokens.push(PtxToken::StringLiteral(argument.clone()));
559                    push_space(tokens, spaced);
560                }
561                if spaced {
562                    if let Some(last) = tokens.last() {
563                        if matches!(last, PtxToken::Space) {
564                            tokens.pop();
565                        }
566                    }
567                }
568            }
569            EntryFunctionHeaderDirective::ReqNctaPerCluster { dim, .. } => {
570                push_directive(tokens, "reqnctapercluster");
571                push_space(tokens, spaced);
572                dim.unparse_tokens_mode(tokens, spaced);
573            }
574            EntryFunctionHeaderDirective::ExplicitCluster { .. } => {
575                push_directive(tokens, "explicitcluster");
576            }
577            EntryFunctionHeaderDirective::MaxClusterRank { value, .. } => {
578                push_directive(tokens, "maxclusterrank");
579                push_space(tokens, spaced);
580                push_decimal(tokens, *value);
581            }
582            EntryFunctionHeaderDirective::BlocksAreClusters { .. } => {
583                push_directive(tokens, "blocksareclusters")
584            }
585        }
586    }
587}
588
589impl PtxUnparser for FuncFunctionHeaderDirective {
590    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
591        self.unparse_tokens_mode(tokens, false);
592    }
593
594    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
595        match self {
596            FuncFunctionHeaderDirective::NoReturn { .. } => push_directive(tokens, "noreturn"),
597            FuncFunctionHeaderDirective::Pragma {
598                args: arguments, ..
599            } => {
600                push_directive(tokens, "pragma");
601                push_space(tokens, spaced);
602                for argument in arguments {
603                    tokens.push(PtxToken::StringLiteral(argument.clone()));
604                    push_space(tokens, spaced);
605                }
606                if spaced {
607                    if let Some(PtxToken::Space) = tokens.last() {
608                        tokens.pop();
609                    }
610                }
611            }
612            FuncFunctionHeaderDirective::AbiPreserve { value, .. } => {
613                push_directive(tokens, "abi_preserve");
614                push_space(tokens, spaced);
615                push_decimal(tokens, *value);
616            }
617            FuncFunctionHeaderDirective::AbiPreserveControl { value, .. } => {
618                push_directive(tokens, "abi_preserve_control");
619                push_space(tokens, spaced);
620                push_decimal(tokens, *value);
621            }
622        }
623    }
624}
625
626impl PtxUnparser for AliasFunctionDirective {
627    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
628        self.unparse_tokens_mode(tokens, false);
629    }
630
631    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
632        push_directive(tokens, "alias");
633        push_space(tokens, spaced);
634        push_identifier(tokens, &self.alias.val);
635        tokens.push(PtxToken::Comma);
636        push_space(tokens, spaced);
637        push_identifier(tokens, &self.target.val);
638        tokens.push(PtxToken::Semicolon);
639        push_newline(tokens, spaced);
640    }
641}
642
643impl PtxUnparser for EntryFunctionDirective {
644    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
645        self.unparse_tokens_mode(tokens, false);
646    }
647
648    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
649        push_directive(tokens, "entry");
650        push_space(tokens, spaced);
651        push_identifier(tokens, &self.name.val);
652        tokens.push(PtxToken::LParen);
653        unparse_param_list(tokens, &self.params, spaced);
654        tokens.push(PtxToken::RParen);
655        for directive in &self.directives {
656            push_newline(tokens, spaced);
657            directive.unparse_tokens_mode(tokens, spaced);
658        }
659        if !self.directives.is_empty() {
660            push_newline(tokens, spaced);
661        }
662        match &self.body {
663            Some(body) => body.unparse_tokens_mode(tokens, spaced),
664            None => {
665                tokens.push(PtxToken::Semicolon);
666                push_newline(tokens, spaced);
667            }
668        }
669    }
670}
671
672impl PtxUnparser for FuncFunctionDirective {
673    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
674        self.unparse_tokens_mode(tokens, false);
675    }
676
677    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
678        push_directive(tokens, "func");
679        for attribute in &self.attributes {
680            push_space(tokens, spaced);
681            attribute.unparse_tokens_mode(tokens, spaced);
682        }
683        if let Some(ret) = &self.return_param {
684            push_space(tokens, spaced);
685            tokens.push(PtxToken::LParen);
686            unparse_param(tokens, ret, spaced);
687            tokens.push(PtxToken::RParen);
688        }
689        push_space(tokens, spaced);
690        push_identifier(tokens, &self.name.val);
691        tokens.push(PtxToken::LParen);
692        unparse_param_list(tokens, &self.params, spaced);
693        tokens.push(PtxToken::RParen);
694        // Design note: function-header directives decorate the completed
695        // signature. Emitting them before `.func` is accepted by this parser's
696        // AST shape but rejected by ptxas (notably for `.noreturn`).
697        for directive in &self.directives {
698            push_space(tokens, spaced);
699            directive.unparse_tokens_mode(tokens, spaced);
700        }
701        // Emit pre-body declarations (.reg, .local, .shared, .param) before the body
702        for decl in &self.pre_body_declarations {
703            push_newline(tokens, spaced);
704            decl.unparse_tokens_mode(tokens, spaced);
705        }
706        match &self.body {
707            Some(body) => body.unparse_tokens_mode(tokens, spaced),
708            None => {
709                tokens.push(PtxToken::Semicolon);
710                push_newline(tokens, spaced);
711            }
712        }
713    }
714}