Code_native diff

I’m often curious whether small changes to functions have any effect on the compiled output. This leads to a lot of cursory inspection of @code_native output, where I’m just looking at the line count and last few lines.

Is there an existing automated way to do this?

Something like the following would be ideal:

julia> @code_native_diff (1+1) (0x1 + 0x1)
false

This workflow would also be good:

julia> original = @code_native_str myfunc(myarg);
# make some inconsequential changes to myfunc
julia> original == @code_native_str myfunc(myarg);
true

Wanted to check here before spending more time on these macros. I’d also be happy to see anyone else’s proposed solution.

Here’s my current progress, which is inflexible and not very user-friendly. I’m also wondering if there’s a better way to capture stdout to a string.

function func_to_str(f, args...; kwargs...)
    let old_stdout = stdout
        rd, = redirect_stdout()
        try
            f(args...; kwargs...)
        finally
            redirect_stdout(old_stdout) # restore original stdout
        end
        output = String(readavailable(rd))
        return output
    end
end

julia> int_add = func_to_str(code_native, +, (Int,); debuginfo=:none)
"\t.text\n\tmovq\t%rdi, %rax\n\tretq\n\tnopw\t%cs:(%rax,%rax)\n"

julia> uint_add = func_to_str(code_native, +, (UInt,); debuginfo=:none)
"\t.text\n\tmovq\t%rdi, %rax\n\tretq\n\tnopw\t%cs:(%rax,%rax)\n"

julia> float_add = func_to_str(code_native, +, (Float64,); debuginfo=:none)
"\t.text\n\tretq\n\tnopw\t%cs:(%rax,%rax)\n"

julia> int_add == uint_add
true

julia> int_add == float_add
false
1 Like

with @code_lowered this is much easier, as they return an object. If the julia AST generated by your function is the same, then the LLVM code and the native code should be the same (i think). here is a macro that compares two expressions to see if they generate the same code:

macro code_diff(exp1,exp2)
    quote 
        a = @code_lowered $exp1;
        b = @code_lowered $exp2;
        a == b
    end
end

julia> @code_diff (1+1) (0x1 + 0x1)
false

The @code_lowered approach does not work as expected. All comparisons are false.

I believe that macro should be changed to a != b anyway to answer the question “are these expressions different?”.

Even if the lowered comparison macro was fixed, it won’t produce a very useful result. There are many situations where different lowered code produces identical native code after LLVM optimizations. A difference in this optimized code is what I’m most interested in detecting.

# --- Identical lowered code reported as different

julia> a = @code_lowered (1 + 1)
CodeInfo(
1 ─ %1 = Base.add_int(x, y)
└──      return %1
)

julia> b = @code_lowered (0x1 + 0x1)
CodeInfo(
1 ─ %1 = Base.add_int(x, y)
└──      return %1
)

julia> a == b
false

# --- Example of different lowered code producing identical native code

function m1(a, b)
    (a + b) * 5
end

function m2(a, b)
    5 * a + 5 * b
end

julia> @code_lowered m1(1, 2)
CodeInfo(
1 ─ %1 = a + b
│   %2 = %1 * 5
└──      return %2
)

julia> @code_lowered m2(1, 2)
CodeInfo(
1 ─ %1 = 5 * a
│   %2 = 5 * b
│   %3 = %1 + %2
└──      return %3
)

julia> @code_native debuginfo=:none m1(1, 2)
        .text
        addq    %rsi, %rdi
        leaq    (%rdi,%rdi,4), %rax
        retq
        nopl    (%rax,%rax)

julia> @code_native debuginfo=:none m2(1, 2)
        .text
        addq    %rsi, %rdi
        leaq    (%rdi,%rdi,4), %rax
        retq
        nopl    (%rax,%rax)

The llvm output might be another nice source to compare, but this has issues with incorrectly reporting identical code as different because of unique line numbers for every function.

julia> int_add = func_to_str(code_llvm, +, (Int,); debuginfo=:none)
"\ndefine i64 @\"julia_+_20082\"(i64) {\ntop:\n  ret i64 %0\n}\n"

julia> uint_add = func_to_str(code_llvm, +, (UInt,); debuginfo=:none)
"\ndefine i64 @\"julia_+_20083\"(i64) {\ntop:\n  ret i64 %0\n}\n"

julia> float_add = func_to_str(code_llvm, +, (Float64,); debuginfo=:none)
"\ndefine double @\"julia_+_20084\"(double) {\ntop:\n  ret double %0\n}\n"

julia> int_add == uint_add
false