How to compile to stand-alone executable using StaticCompiler in Windows?

I published this question already in https://stackoverflow.com/questions/77551695/how-to-compile-julia-code-as-stand-alone-executable-with-help-of-staticcompiler.
I’d like to compile to small executable in Windows, e.g. with help of StaticCompiler. StaticCompiler is successful for Linux, and it would be great, if it can be used in Windows, too. The problem is that the generated executable does not print a string (neither to stdout nor to a file). Do you have any experiences or suggestions to get StaticCompiler or GPUCompiler work to print a string under windows?

For example, I tried:

using StaticCompiler, StaticTools
f() = println(c"h")
compile_executable(f,(),"./")

This produces an object file (“f.o”), but linking fails (linking the objective file with a wrapper-file, wrapper.c, to create an executable). StaticCompiler uses clang for linking. Clang fails here, prompting the error message “f.o unknown file type”. The developers of StaticCompiler recommended trying the compiler that PackageCompiler installs as an artifact. This is in my case gcc, and indeed gcc links without error.

However, the generated executable does not print anything to stdout. But generally the executable seems to work. E.g. compiling f() = getchar() works.

Under the hood, StaticTools translates println to a LLVM IR that calls @puts. For example, putting ‘h’ would be the Julia LLVM IR

function f()
  Base.llvmcall(("""
    declare i32 @puts(i8*)
    define i32 @main() {
      %a = alloca i8
      store i8 104, i8* %a
      call i32 @puts(i8* %a)
      ret i32 0
    }
  """, "main"), Int32, Nothing, Nothing)
end

This works in the Repl, but when applying compile_executable, the generated executable does not print anything to stdout.

StaticCompiler uses GPUCompiler to produce a LLVM IR module that is then translated in machine code. In this case, the LLVM IR module produced from the LLVM IR depicted above is:

source_filename = "start"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-w64-mingw32"

define i32 @t() local_unnamed_addr #0 {
top:
  %0 = call fastcc i32 @julia_t_1040u1042()
  ret i32 %0
}

define internal fastcc i32 @julia_t_1040u1042() unnamed_addr {
  %a = alloca i8, align 1
  store i8 65, i8* %a, align 1
  %1 = call i32 @puts(i8* noundef nonnull %a)
  ret i32 0
}

declare i32 @puts(i8*) local_unnamed_addr

attributes #0 = { "probe-stack"="inline-asm" }

!llvm.module.flags = !{!0, !1}

!0 = !{i32 2, !"Dwarf Version", i32 4}
!1 = !{i32 2, !"Debug Info Version", i32 3}

My questions: Why does StaticCompiler fails printing output in Windows? Does the Julia LLVM IR, or the string representation in StaticTools, or the GPUCompiler configuration or implementation, or some environment variables, have to be adapted for Windows?

1 Like

StaticCompiler.jl states one if its limitations is “Doesn’t work on Windows (but works in WSL on Windows 10+). PRs welcome.” in its README.

1 Like

I put a littile thought into this a year ago:

My thought is that the easiest path may be through a MSYS2 like environment for native Windows. WSL2 might be an easier alternative for now.

Our goal is to overcome this limitation and to support StaticCompiler to work on Windows. I imagine, there are some people using Windows who want to apply StaticCompiler.

2 Likes

Are you representing some larger group or company?

I would be happy to collaborate on this. I’m probably one of thr few who uses Windows regularly while also having some limited knowledge on Julia internals.

1 Like

We are not a larger group or a company, but two people.
We would be very happy to collaborate with you. We are highly interested to support people who want to use Julia on Windows. We think the whole Julia community would benefit from this.

7 Likes

Here are some further attempts: I have tried to generate a LLVM IR from a simple C-program (the way (B) Brehnin Keller suggested in the issues of StaticTools
https://github.com/brenhinkeller/StaticTools.jl/issues/20).

I used a simple C program “put.c” that writes an “A” to stdout:

#include <stdio.h>

int main(void) {
   printf("A");
}

Using clang -emit-llvm -S put.c -o put.ll I got the following LLVM IR:

@.str = private unnamed_addr constant [2 x i8] c"A\00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
  %1 = call i32 (ptr, ...) @printf(ptr noundef @.str)
  ret i32 0
}

declare dso_local i32 @printf(ptr noundef, ...) #1

attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3}
!llvm.ident = !{!4}

!0 = !{i32 1, !"wchar_size", i32 2}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 2}
!3 = !{i32 1, !"MaxTLSAlign", i32 65536}
!4 = !{!"clang version 17.0.4 (https://github.com/llvm/llvm-project.git 309d55140c46384b6de7a7573206cbeba3f7077f)"}

I put this in a Base.llvmcall() of Julia with some slight adaptions: The constant c"A\00" did not work, Julia came up with some syntax error.
Also with the type ptr an error occurred . So I used i8* instead. The adapted LLVM IR for Julia is the following:

function t()
Base.llvmcall(("""	

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
%a = alloca i64
store i64 65, i64* %a
  %1 = call i32 (i64*, ...) @printf(i64* noundef %a)
  ret i32 0
}

declare dso_local i32 @printf(i64* noundef, ...) #1

attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3}
!llvm.ident = !{!4}

!0 = !{i32 1, !"wchar_size", i32 2}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 2}
!3 = !{i32 1, !"MaxTLSAlign", i32 65536}
!4 = !{!"clang version 17.0.4 (https://github.com/llvm/llvm-project.git 309d55140c46384b6de7a7573206cbeba3f7077f)"}
               """, "main"), Int32, Nothing, Nothing)
end

Then I applied

using StaticTools, StaticCompiler
compile_executable(t,(),"./")

resulting in an object file t.o. After linking with gcc an executable is generated.

gcc wrapper.c t.o -o t.exe

However, the executalbe t.exe did not give an output. The returned error code is “False”.

When applying clang for linking, I got
"t.o unknown file type"

When compiling a function like
t() = getchar()
the generated executable works (expecting an input). Only outputting to stdout or to a file does not work and results in an error. To me, this suggests, that the LLVM IR might be wrong.
Do you have any suggestions how to make the executable could print a string, or how the LLVM IR should look like?

1 Like

The solution for adapting StaticCompiler for Windows is running llc to get the native object file from the LLVM IR, instead of using GPUCompiler. llc is the static compiler from LLVM. We used the following code in the function generate_obj():


if Sys.iswindows()
      ir_path = joinpath(path, "$filenamebase.ll")
      open(ir_path, "w") do io
        write(io, string(mod))
      end
      run(`cmd /c llc  -filetype=obj -mtriple=x86_64-w64-windows-gnu $ir_path -o $obj_path`)

Meanwhile llc is integrated in clang. So it suffices in the function generate_obj():

if Sys.iswindows()
      ir_path = joinpath(path, "$filenamebase.ll")
      open(ir_path, "w") do io
        write(io, string(mod))
      end
...
end

and in the function generate_executable():


if Sys.iswindows()
          ir_path = joinpath(path, "$filename.ll")
          run(`cmd /c clang -Wno-override-module $wrapper_path $ir_path -o $exec_path`
...
end

We are going to implement the adaptions to windows in a PR for StaticCompiler.

8 Likes

Here is a repo that is a fork of StaticCompiler containing those adaptions to Windows.
It requires clang (the clang delivered in the artifacts does not work on my machine (because of new Julia version I use, and so on).

6 Likes