Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic MinGW-w64 cross-compilation support #15070

Merged
merged 11 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/big/big_int.cr
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ struct BigInt < Int
{% for n in [8, 16, 32, 64, 128] %}
def to_i{{n}} : Int{{n}}
\{% if Int{{n}} == LibGMP::SI %}
LibGMP.{{ flag?(:win32) ? "fits_si_p".id : "fits_slong_p".id }}(self) != 0 ? LibGMP.get_si(self) : raise OverflowError.new
LibGMP.{{ flag?(:win32) && !flag?(:gnu) ? "fits_si_p".id : "fits_slong_p".id }}(self) != 0 ? LibGMP.get_si(self) : raise OverflowError.new
\{% elsif Int{{n}}::MAX.is_a?(NumberLiteral) && Int{{n}}::MAX < LibGMP::SI::MAX %}
LibGMP::SI.new(self).to_i{{n}}
\{% else %}
Expand All @@ -669,7 +669,7 @@ struct BigInt < Int

def to_u{{n}} : UInt{{n}}
\{% if UInt{{n}} == LibGMP::UI %}
LibGMP.{{ flag?(:win32) ? "fits_ui_p".id : "fits_ulong_p".id }}(self) != 0 ? LibGMP.get_ui(self) : raise OverflowError.new
LibGMP.{{ flag?(:win32) && !flag?(:gnu) ? "fits_ui_p".id : "fits_ulong_p".id }}(self) != 0 ? LibGMP.get_ui(self) : raise OverflowError.new
\{% elsif UInt{{n}}::MAX.is_a?(NumberLiteral) && UInt{{n}}::MAX < LibGMP::UI::MAX %}
LibGMP::UI.new(self).to_u{{n}}
\{% else %}
Expand Down
8 changes: 4 additions & 4 deletions src/big/lib_gmp.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% if flag?(:win32) %}
{% if flag?(:win32) && !flag?(:gnu) %}
@[Link("mpir")]
{% if compare_versions(Crystal::VERSION, "1.11.0-dev") >= 0 %}
@[Link(dll: "mpir.dll")]
Expand All @@ -14,7 +14,7 @@ lib LibGMP
# MPIR uses its own `mpir_si` and `mpir_ui` typedefs in places where GMP uses
# the LibC types, when the function name has `si` or `ui`; we follow this
# distinction
{% if flag?(:win32) && flag?(:bits64) %}
{% if flag?(:win32) && !flag?(:gnu) && flag?(:bits64) %}
alias SI = LibC::LongLong
alias UI = LibC::ULongLong
{% else %}
Expand All @@ -26,7 +26,7 @@ lib LibGMP
alias Double = LibC::Double
alias BitcntT = UI

{% if flag?(:win32) && flag?(:bits64) %}
{% if flag?(:win32) && !flag?(:gnu) && flag?(:bits64) %}
alias MpExp = LibC::Long
alias MpSize = LibC::LongLong
alias MpLimb = LibC::ULongLong
Expand Down Expand Up @@ -151,7 +151,7 @@ lib LibGMP

fun fits_ulong_p = __gmpz_fits_ulong_p(op : MPZ*) : Int
fun fits_slong_p = __gmpz_fits_slong_p(op : MPZ*) : Int
{% if flag?(:win32) %}
{% if flag?(:win32) && !flag?(:gnu) %}
fun fits_ui_p = __gmpz_fits_ui_p(op : MPZ*) : Int
fun fits_si_p = __gmpz_fits_si_p(op : MPZ*) : Int
{% end %}
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/crystal/codegen/codegen.cr
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ module Crystal
@main = @llvm_mod.functions.add(MAIN_NAME, main_type)
@fun_types = { {@llvm_mod, MAIN_NAME} => main_type }

if @program.has_flag? "windows"
if @program.has_flag?("windows") && [email protected]_flag?("gnu")
HertzDevil marked this conversation as resolved.
Show resolved Hide resolved
@personality_name = "__CxxFrameHandler3"
@main.personality_function = windows_personality_fun.func
else
Expand Down Expand Up @@ -2488,7 +2488,7 @@ module Crystal
end

def self.safe_mangling(program, name)
if program.has_flag?("windows")
if program.has_flag?("windows") && !program.has_flag?("gnu")
String.build do |str|
name.each_char do |char|
if char.ascii_alphanumeric? || char == '_'
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/codegen/debug.cr
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module Crystal
def push_debug_info_metadata(mod)
di_builder(mod).end

if @program.has_flag?("windows")
if @program.has_flag?("windows") && [email protected]_flag?("gnu")
# Windows uses CodeView instead of DWARF
mod.add_flag(
LibLLVM::ModuleFlagBehavior::Warning,
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/crystal/codegen/exception.cr
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Crystal::CodeGenVisitor
#
# Note we codegen the ensure body three times! In practice this isn't a big deal, since ensure bodies are typically small.

windows = @program.has_flag? "windows"
windows = @program.has_flag?("windows") && [email protected]_flag?("gnu")

context.fun.personality_function = windows_personality_fun.func if windows

Expand Down Expand Up @@ -283,7 +283,7 @@ class Crystal::CodeGenVisitor
end

def codegen_re_raise(node, unwind_ex_obj)
if @program.has_flag? "windows"
if @program.has_flag?("windows") && [email protected]_flag?("gnu")
# On windows we can re-raise by calling _CxxThrowException with two null arguments
call windows_throw_fun, [llvm_context.void_pointer.null, llvm_context.void_pointer.null]
unreachable
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/codegen/link.cr
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ module Crystal

class Program
def lib_flags
has_flag?("windows") ? lib_flags_windows : lib_flags_posix
has_flag?("windows") && !has_flag?("gnu") ? lib_flags_windows : lib_flags_posix
end

private def lib_flags_windows
Expand Down
47 changes: 42 additions & 5 deletions src/crystal/system/win32/thread.cr
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,49 @@ module Crystal::System::Thread
LibC.SwitchToThread
end

@[ThreadLocal]
class_property current_thread : ::Thread { ::Thread.new }
# MinGW does not support TLS correctly
{% if flag?(:gnu) %}
@@current_key : LibC::DWORD = begin
current_key = LibC.TlsAlloc
if current_key == LibC::TLS_OUT_OF_INDEXES
Crystal::System.panic("TlsAlloc()", WinError.value)
end
current_key
end

def self.current_thread? : ::Thread?
@@current_thread
end
def self.current_thread : ::Thread
th = current_thread?
return th if th

# Thread#start sets `Thread.current` as soon it starts. Thus we know
# that if `Thread.current` is not set then we are in the main thread
self.current_thread = ::Thread.new
end

def self.current_thread? : ::Thread?
ptr = LibC.TlsGetValue(@@current_key)
err = WinError.value
unless err == WinError::ERROR_SUCCESS
Crystal::System.panic("TlsGetValue()", err)
end

ptr.as(::Thread?)
end

def self.current_thread=(thread : ::Thread)
if LibC.TlsSetValue(@@current_key, thread.as(Void*)) == 0
Crystal::System.panic("TlsSetValue()", WinError.value)
end
thread
end
{% else %}
@[ThreadLocal]
class_property current_thread : ::Thread { ::Thread.new }

def self.current_thread? : ::Thread?
@@current_thread
end
{% end %}

def self.sleep(time : ::Time::Span) : Nil
LibC.Sleep(time.total_milliseconds.to_i.clamp(1..))
Expand Down
3 changes: 3 additions & 0 deletions src/exception/call_stack.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
require "./call_stack/interpreter"
{% elsif flag?(:win32) %}
require "./call_stack/stackwalk"
{% if flag?(:gnu) %}
require "./lib_unwind"
{% end %}
{% elsif flag?(:wasm32) %}
require "./call_stack/null"
{% else %}
Expand Down
27 changes: 27 additions & 0 deletions src/exception/call_stack/stackwalk.cr
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,33 @@ struct Exception::CallStack
end
end

# TODO: needed only if `__crystal_raise` fails, check if this actually works
{% if flag?(:gnu) %}
def self.print_backtrace : Nil
backtrace_fn = ->(context : LibUnwind::Context, data : Void*) do
last_frame = data.as(RepeatedFrame*)

ip = {% if flag?(:arm) %}
Pointer(Void).new(__crystal_unwind_get_ip(context))
{% else %}
Pointer(Void).new(LibUnwind.get_ip(context))
{% end %}

if last_frame.value.ip == ip
last_frame.value.incr
else
print_frame(last_frame.value) unless last_frame.value.ip.address == 0
last_frame.value = RepeatedFrame.new ip
end
LibUnwind::ReasonCode::NO_REASON
end

rf = RepeatedFrame.new(Pointer(Void).null)
LibUnwind.backtrace(backtrace_fn, pointerof(rf).as(Void*))
print_frame(rf)
end
{% end %}

private def self.print_frame(repeated_frame)
Crystal::System.print_error "[%p] ", repeated_frame.ip
print_frame_location(repeated_frame)
Expand Down
8 changes: 6 additions & 2 deletions src/exception/lib_unwind.cr
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ lib LibUnwind
struct Exception
exception_class : LibC::SizeT
exception_cleanup : LibC::SizeT
private1 : UInt64
private2 : UInt64
{% if flag?(:win32) && flag?(:gnu) %}
private_ : UInt64[6]
{% else %}
private1 : UInt64
private2 : UInt64
{% end %}
exception_object : Void*
exception_type_id : Int32
end
Expand Down
1 change: 1 addition & 0 deletions src/lib_c/x86_64-windows-gnu
6 changes: 6 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,11 @@ lib LibC
fun ResumeThread(hThread : HANDLE) : DWORD
fun SuspendThread(hThread : HANDLE) : DWORD

TLS_OUT_OF_INDEXES = 0xFFFFFFFF_u32

fun TlsAlloc : DWORD
fun TlsGetValue(dwTlsIndex : DWORD) : Void*
fun TlsSetValue(dwTlsIndex : DWORD, lpTlsValue : Void*) : BOOL

PROCESS_QUERY_INFORMATION = 0x0400
end
2 changes: 1 addition & 1 deletion src/llvm/target_machine.cr
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class LLVM::TargetMachine
def abi
triple = self.triple
case triple
when /x86_64.+windows-msvc/
when /x86_64.+windows-(?:msvc|gnu)/
ABI::X86_Win64.new(self)
when /x86_64|amd64/
ABI::X86_64.new(self)
Expand Down
29 changes: 24 additions & 5 deletions src/raise.cr
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ end

{% if flag?(:interpreted) %}
# interpreter does not need `__crystal_personality`
{% elsif flag?(:win32) %}
{% elsif flag?(:win32) && !flag?(:gnu) %}
require "exception/lib_unwind"

{% begin %}
Expand Down Expand Up @@ -181,8 +181,10 @@ end
0u64
end
{% else %}
# :nodoc:
fun __crystal_personality(version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*) : LibUnwind::ReasonCode
{% mingw = flag?(:windows) && flag?(:gnu) %}
fun {{ mingw ? "__crystal_personality_imp".id : "__crystal_personality".id }}(
version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*,
) : LibUnwind::ReasonCode
start = LibUnwind.get_region_start(context)
ip = LibUnwind.get_ip(context)
lsd = LibUnwind.get_language_specific_data(context)
Expand All @@ -197,9 +199,26 @@ end

return LibUnwind::ReasonCode::CONTINUE_UNWIND
end

{% if mingw %}
lib LibC
alias EXCEPTION_DISPOSITION = Int
alias DISPATCHER_CONTEXT = Void
end

lib LibUnwind
alias PersonalityFn = Int32, Action, UInt64, Exception*, Void* -> ReasonCode

fun _GCC_specific_handler(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*, gcc_per : PersonalityFn) : LibC::EXCEPTION_DISPOSITION
end

fun __crystal_personality(ms_exc : LibC::EXCEPTION_RECORD64*, this_frame : Void*, ms_orig_context : LibC::CONTEXT*, ms_disp : LibC::DISPATCHER_CONTEXT*) : LibC::EXCEPTION_DISPOSITION
LibUnwind._GCC_specific_handler(ms_exc, this_frame, ms_orig_context, ms_disp, ->__crystal_personality_imp)
end
{% end %}
{% end %}

{% unless flag?(:interpreted) || flag?(:win32) || flag?(:wasm32) %}
{% unless flag?(:interpreted) || (flag?(:win32) && !flag?(:gnu)) || flag?(:wasm32) %}
# :nodoc:
@[Raises]
fun __crystal_raise(unwind_ex : LibUnwind::Exception*) : NoReturn
Expand Down Expand Up @@ -244,7 +263,7 @@ def raise(message : String) : NoReturn
raise Exception.new(message)
end

{% if flag?(:win32) %}
{% if flag?(:win32) && !flag?(:gnu) %}
# :nodoc:
{% if flag?(:interpreted) %} @[Primitive(:interpreter_raise_without_backtrace)] {% end %}
def raise_without_backtrace(exception : Exception) : NoReturn
Expand Down
Loading