# gccrs's inline asm mov issue Hi everyone, this article details the progress on the inline asm project in gccrs as well as asks for help debugging the issue of tree IR in generating assembly. The write up contains C and rust test file, tree IR generation for both test file, with diff between, along with assembly. ## Project structure The related branch is `asm_generic_il` PR https://github.com/Rust-GCC/gccrs/pull/3060 Since there might be a lot of force pushes, the related identifying title in the PR is ` Use's array type when constring string tree ` The related files for this issue includes - gcc/rust/expand/rust-macrobuiltins-asm.h/cc: Where the parsing of an `asm!()` happens. - gcc/rust/ast/rust-expr.h: Where the ast of an inline asm is defined. - gcc/rust/hir/tree/rust-hir-expr.h: Where the HIR (high level ir) of an inline asm is defined. - gcc/rust/backend/rust-compile-asm.h/cc: where the backend of inline asm starts processing. The progression of an `asm!` approximately looks like this ```mermaid flowchart TD %% Nodes A("asm!"):::green B("parsing"):::orange C("AST"):::blue D{"HIR"}:::yellow E("backend tree"):::pink F("gimplifier"):::purple G("assembly code") %% Edges A --> B -->|Via rust-macrobuiltins.h/cc| C -->|lowering necessary parts| D D -->|lowering in rust-compile-asm.h/cc| E -->|tree ir| F F -->|/not the domain of concern/|G %% Styling classDef green fill:#B2DFDB,stroke:#00897B,stroke-width:2px; classDef orange fill:#FFE0B2,stroke:#FB8C00,stroke-width:2px; classDef blue fill:#BBDEFB,stroke:#1976D2,stroke-width:2px; classDef yellow fill:#FFF9C4,stroke:#FBC02D,stroke-width:2px; classDef pink fill:#F8BBD0,stroke:#C2185B,stroke-width:2px; classDef purple fill:#E1BEE7,stroke:#8E24AA,stroke-width:2px; ``` ## Assumption and test files ### Assumption The assumption that gccrs to generate the correct assembly is that for a test file, we look at a `.c` file and a `.rs` file and their tree, gimple and asm output from their respective gcc and gccrs compiler. - For tree IR, we use `debug` right after we generate a tree asm in gcc and gccrs to inspect them. - For gimple, we invoke the command line option `-fdump-tree-original` from gcc and gccrs to inspect them. - For asm output, we look to see if the inline assembly we pass in from the `asm!` invocation is inserted between others assembly. The end expectation is > so i was expecting this test file (rust file) to execute correctly (no error) and that `_x` has the value of 42 in it. This means that in the assembly file, with reg option i'm expecting the %0 in the assembly string to be transformed into a register like x0 or something on ARM ### Test files The test file from c and rust involves their respective invocation. For c file `asm_mov.c`: ```c #include <stdio.h> int main() { int output1 = 0, output2; asm("mov %0, 42\n" // Move constant 42 to output1 : "=r"(output1)); return 0; } ``` For rust file `asm_mov.rs`: ```rust #![feature(rustc_attrs)] #[rustc_builtin_macro] macro_rules! asm { () => {}; } extern "C" { fn printf(s: *const i8, ...); } fn main() { let mut _x: i32 = 0; unsafe { asm!( "mov %0, 42", out(reg) _x ); } } ``` ### Test file notes The assembly string in rust doesn't follow the specs in https://doc.rust-lang.org/reference/inline-assembly.html, refering to \_x as %0 instead of {} This is just to guarantee that when the string in `asm!` goes through the tree ir construction and eventually the gimplifier, it is also %0. ## Tree inspection Files: - C's output of tree: tree_c.txt - rust's output of tree: tree_rs.txt - C's assembly output: asm_mov.s - rust's assembly output: asm_mov_rs.s ### gcc We modify the gcc/gccrs codebase so that after each respective compiler produces the tree IR right after they finish building the asm tree expr For gcc, we modify `c_parser_asm_statement` in `gcc/c/c-parser.cc` to debug the tree, using debug_tree from print-tree ```diff #include "run-rtl-passes.h" #include "intl.h" #include "c-family/name-hint.h" #include "tree-iterator.h" +#include "print-tree.h" #include "tree-pretty-print.h" #include "memmodel.h" #include "c-family/known-headers.h" ... ... ... // In c_parser_asm_statement() ... if (!c_parser_require (parser, CPP_SEMICOLON, "expected %<;%>")) c_parser_skip_to_end_of_block_or_statement (parser); ret = build_asm_stmt (is_volatile, build_asm_expr (asm_loc, str, outputs, inputs, clobbers, labels, simple, is_inline)); + debug_tree (ret); error: return ret; error_close_paren: c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, NULL); goto error; } ``` The tree output from the compiler with ` ../gccrs-install/bin/gcc -S asm_mov.c > tree_c.txt 2>&1 `: (we will compare this against gccrs's tree output): ```< <asm_expr 0xffffb02ad240 type <void_type 0xffffb02b4f18 void VOID align:8 warn_if_not_align:0 symtab:0 alias-set -1 canonical-type 0xffffb02b4f18 pointer_to_this <pointer_type 0xffffb02bc000>> side-effects arg:0 <string_cst 0xffffb0224d20 type <array_type 0xffffb0222888 type <integer_type 0xffffb02b43f0 char> BLK size <integer_cst 0xffffb02b7378 constant 96> unit-size <integer_cst 0xffffb0200660 constant 12> align:8 warn_if_not_align:0 symtab:0 alias-set -1 canonical-type 0xffffb0222888 domain <integer_type 0xffffb02227e0>> readonly constant static "mov %0, 42\012\000"> arg:1 <tree_list 0xffffb0224d70 purpose <tree_list 0xffffb0224d48 value <string_cst 0xffffb0200690 type <array_type 0xffffb02229d8> readonly constant static "=r\000">> value <var_decl 0xffffb01e32d0 output1 type <integer_type 0xffffb02b45e8 int> used read SI asm_mov.c:4:7 size <integer_cst 0xffffb02b7150 constant 32> unit-size <integer_cst 0xffffb02b7168 constant 4> align:32 warn_if_not_align:0 context <function_decl 0xffffb0226200 main> initial <integer_cst 0xffffb02b72a0 0>>> asm_mov.c:6:3 start: asm_mov.c:6:3 finish: asm_mov.c:6:5> ``` Inspection of the assembly file via `cat asm_mov.s` reveals that the correct strings that is passed into the `__asm__()` call is transformed successfully with the register x0: ```diff .arch armv8-a .file "asm_mov.c" .text .section .rodata .align 3 .LC0: .string "Output 1: %d\n" .text .align 2 .global main .type main, %function main: .LFB0: .cfi_startproc stp x29, x30, [sp, -32]! .cfi_def_cfa_offset 32 .cfi_offset 29, -32 .cfi_offset 30, -24 mov x29, sp str wzr, [sp, 28] #APP // 6 "asm_mov.c" 1 + mov x0, 42 // 0 "" 2 #NO_APP str w0, [sp, 28] ldr w1, [sp, 28] adrp x0, .LC0 add x0, x0, :lo12:.LC0 bl printf mov w0, 0 ldp x29, x30, [sp], 32 .cfi_restore 30 .cfi_restore 29 .cfi_def_cfa_offset 0 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (GNU) 14.0.1 20240309 (experimental)" .section .note.GNU-stack,"",@progbits ``` ### gccrs For gccrs, all the related functions for generating tree ir is in `gcc/rust/backend/rust-compile-asm.cc`. We also use `debug` from the Backend:: ```diff tree CompileAsm::asm_build_expr (HIR::InlineAsm &expr) { auto asm_expr = asm_build_stmt (expr.get_locus (), {asm_construct_string_tree (expr), asm_construct_outputs (expr), asm_construct_inputs (expr), asm_construct_clobber_tree (expr), asm_construct_label_tree (expr)}); ASM_INPUT_P (asm_expr) = expr.is_simple_asm (); ASM_VOLATILE_P (asm_expr) = false; ASM_INLINE_P (asm_expr) = expr.is_inline_asm (); + Backend::debug (asm_expr); return asm_expr; } ``` gccrs spits out its tree ir: ```< <asm_expr 0xffff903c1a00 type <void_type 0xffff903aef18 VOID align:8 warn_if_not_align:0 symtab:0 alias-set -1 canonical-type 0xffff903aef18 pointer_to_this <pointer_type 0xffff903b6000>> side-effects protected static arg:0 <string_cst 0xffff9039b9e0 type <array_type 0xffff903c3d20 type <integer_type 0xffff903c3c78> string-flag BLK size <integer_cst 0xffff903b31f8 constant 96> unit-size <integer_cst 0xffff903b38b8 constant 12> align:8 warn_if_not_align:0 symtab:0 alias-set -1 canonical-type 0xffff903c3d20 domain <integer_type 0xffff903c3bd0>> constant "mov %0, 42\012"> arg:1 <tree_list 0xffff903bf938 purpose <tree_list 0xffff903bf910 value <string_cst 0xffff903b38e8 constant "=r\000">> value <var_decl 0xffff903cb090 _x type <integer_type 0xffff903ae5e8 i32> used SI asm_mov.rs:12:9 size <integer_cst 0xffff9039afc0 constant 32> unit-size <integer_cst 0xffff9039afd8 constant 4> align:32 warn_if_not_align:0 context <function_decl 0xffff903c6100 asm_mov::main> initial <integer_cst 0xffff903b3120 0>>> asm_mov.rs:14:9 start: asm_mov.rs:14:9 finish: asm_mov.rs:14:11> ``` With a message of assembler error ``` /tmp/ccv5FWsb.s: Assembler messages: /tmp/ccv5FWsb.s:15: Error: operand 1 must be an integer register -- `mov %0,42' ``` Inspection shows the string inputted from the `asm!()` macro hasn't been transformed. The %0 hasn't been transformed to x0 in ARM (on my macbook) ```diff .arch armv8-a .file "asm_mov.rs" .text .align 2 .global main .type main, %function main: .LFB0: .cfi_startproc sub sp, sp, #16 .cfi_def_cfa_offset 16 str wzr, [sp, 12] #APP // 14 "asm_mov.rs" 1 + mov %0, 42 // 0 "" 2 #NO_APP add sp, sp, 16 .cfi_def_cfa_offset 0 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (GNU) 14.0.1 20240309 (experimental)" . ``` ### diff of trees The diff of two tree IRs are in the following link https://www.diffchecker.com/Z10caKQa/ Besides the difference in addresses, for example `0xffffb618d240` versus `0xffffaecd1a00`, there are some notable differences - side effects: c version doens't have any side effects, it has `protected` and `static` - rust has `string flag` tag on line 8, before the word BLK?! - The string in rust version is of type `constant`, where in c, it is `readonly constant static` - In arg1, rust's arg's purpose's value is a string cst of constant, where in c, it is `string cst` of `array type` of `readonly constant static` - The refered value of `_x` in rust (line 18) is ### analysis In gccrs, instead of wrapping an build_stmt around a build_expr, we wrap a build_expr around a build_stmt, just because InlineAsm is an expr. The functionality should stay the same. *to be continue* ## Revisions # ASM_INPUT_P to false Sovled issue, will write credit and name asshole ## Credits credit: Arthur, Pierre-Emmanuel, Arsen, rsandifo, pinskia (a.k.a @norwoodites)