diff --git a/.github/workflows/mingw-w64.yml b/.github/workflows/mingw-w64.yml new file mode 100644 index 000000000000..2370ae133cdd --- /dev/null +++ b/.github/workflows/mingw-w64.yml @@ -0,0 +1,91 @@ +name: MinGW-w64 CI + +on: [push, pull_request] + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + x86_64-mingw-w64-cross-compile: + runs-on: ubuntu-24.04 + steps: + - name: Download Crystal source + uses: actions/checkout@v4 + + - name: Install LLVM 18 + run: | + sudo apt remove 'llvm-*' 'libllvm*' + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc + sudo apt-add-repository -y deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main + sudo apt install -y llvm-18-dev + + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: "1.14.0" + + - name: Cross-compile Crystal + run: make && make -B target=x86_64-windows-gnu release=1 + + - name: Upload crystal.obj + uses: actions/upload-artifact@v4 + with: + name: x86_64-mingw-w64-crystal-obj + path: .build/crystal.obj + + - name: Upload standard library + uses: actions/upload-artifact@v4 + with: + name: x86_64-mingw-w64-crystal-stdlib + path: src + + x86_64-mingw-w64-link: + runs-on: windows-2022 + needs: [x86_64-mingw-w64-cross-compile] + steps: + - name: Setup MSYS2 + id: msys2 + uses: msys2/setup-msys2@ddf331adaebd714795f1042345e6ca57bd66cea8 # v2.24.1 + with: + msystem: UCRT64 + update: true + install: >- + mingw-w64-ucrt-x86_64-pkgconf + mingw-w64-ucrt-x86_64-cc + mingw-w64-ucrt-x86_64-gc + mingw-w64-ucrt-x86_64-pcre2 + mingw-w64-ucrt-x86_64-libiconv + mingw-w64-ucrt-x86_64-zlib + mingw-w64-ucrt-x86_64-llvm + + - name: Download crystal.obj + uses: actions/download-artifact@v4 + with: + name: x86_64-mingw-w64-crystal-obj + + - name: Download standard library + uses: actions/download-artifact@v4 + with: + name: x86_64-mingw-w64-crystal-stdlib + path: share/crystal/src + + - name: Link Crystal executable + shell: msys2 {0} + run: | + mkdir bin + cc crystal.obj -o bin/crystal.exe \ + $(pkg-config bdw-gc libpcre2-8 iconv zlib --libs) \ + $(llvm-config --libs --system-libs --ldflags) \ + -lDbgHelp -lole32 -lWS2_32 -Wl,--stack,0x800000 + ldd bin/crystal.exe | grep -iv /c/windows/system32 | sed 's/.* => //; s/ (.*//' | xargs -t -i cp '{}' bin/ + + - name: Upload Crystal + uses: actions/upload-artifact@v4 + with: + name: x86_64-mingw-w64-crystal + path: | + bin/ + share/ diff --git a/spec/compiler/codegen/pointer_spec.cr b/spec/compiler/codegen/pointer_spec.cr index 1230d80cb5f6..da132cdee406 100644 --- a/spec/compiler/codegen/pointer_spec.cr +++ b/spec/compiler/codegen/pointer_spec.cr @@ -492,28 +492,33 @@ describe "Code gen: pointer" do )).to_b.should be_true end - it "takes pointerof lib external var" do - test_c( - %( - int external_var = 0; - ), - %( - lib LibFoo - $external_var : Int32 - end - - LibFoo.external_var = 1 - - ptr = pointerof(LibFoo.external_var) - x = ptr.value - - ptr.value = 10 - y = ptr.value - - ptr.value = 100 - z = LibFoo.external_var - - x + y + z - ), &.to_i.should eq(111)) - end + # FIXME: `$external_var` implies __declspec(dllimport), but we only have an + # object file, so MinGW-w64 fails linking (actually MSVC also emits an + # LNK4217 linker warning) + {% unless flag?(:win32) && flag?(:gnu) %} + it "takes pointerof lib external var" do + test_c( + %( + int external_var = 0; + ), + %( + lib LibFoo + $external_var : Int32 + end + + LibFoo.external_var = 1 + + ptr = pointerof(LibFoo.external_var) + x = ptr.value + + ptr.value = 10 + y = ptr.value + + ptr.value = 100 + z = LibFoo.external_var + + x + y + z + ), &.to_i.should eq(111)) + end + {% end %} end diff --git a/spec/compiler/codegen/thread_local_spec.cr b/spec/compiler/codegen/thread_local_spec.cr index 694cb430b8c1..386043f2c5fd 100644 --- a/spec/compiler/codegen/thread_local_spec.cr +++ b/spec/compiler/codegen/thread_local_spec.cr @@ -1,4 +1,4 @@ -{% skip_file if flag?(:openbsd) %} +{% skip_file if flag?(:openbsd) || (flag?(:win32) && flag?(:gnu)) %} require "../../spec_helper" diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr index c01fb0ff6b8a..7c6f5d746bdc 100644 --- a/spec/std/exception/call_stack_spec.cr +++ b/spec/std/exception/call_stack_spec.cr @@ -12,9 +12,9 @@ describe "Backtrace" do _, output, _ = compile_and_run_file(source_file) - # resolved file:line:column (no column for windows PDB because of poor - # support in general) - {% if flag?(:win32) %} + # resolved file:line:column (no column for MSVC PDB because of poor support + # by external tooling in general) + {% if flag?(:msvc) %} output.should match(/^#{Regex.escape(source_file)}:3 in 'callee1'/m) output.should match(/^#{Regex.escape(source_file)}:13 in 'callee3'/m) {% else %} diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 3be5c07e1479..620a1d034d9f 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -736,7 +736,7 @@ describe IO do it "says invalid byte sequence" do io = SimpleIOMemory.new(Slice.new(1, 255_u8)) io.set_encoding("EUC-JP") - expect_raises ArgumentError, {% if flag?(:musl) || flag?(:freebsd) %}"Incomplete multibyte sequence"{% else %}"Invalid multibyte sequence"{% end %} do + expect_raises ArgumentError, {% if flag?(:musl) || flag?(:freebsd) || flag?(:netbsd) %}"Incomplete multibyte sequence"{% else %}"Invalid multibyte sequence"{% end %} do io.read_char end end diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index f41529af901a..f8e4ff1e8ae2 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -8,6 +8,14 @@ describe "PROGRAM_NAME" do pending! "Example is broken in Nix shell (#12332)" end + # MSYS2: gcc/ld doesn't support unicode paths + # https://github.com/msys2/MINGW-packages/issues/17812 + {% if flag?(:windows) %} + if ENV["MSYSTEM"]? + pending! "Example is broken in MSYS2 shell" + end + {% end %} + File.write(source_file, "File.basename(PROGRAM_NAME).inspect(STDOUT)") compile_file(source_file, bin_name: "×‽😂") do |executable_file| @@ -254,38 +262,44 @@ describe "hardware exception" do error.should_not contain("Stack overflow") end - it "detects stack overflow on the main stack", tags: %w[slow] do - # This spec can take some time under FreeBSD where - # the default stack size is 0.5G. Setting a - # smaller stack size with `ulimit -s 8192` - # will address this. - status, _, error = compile_and_run_source <<-'CRYSTAL' - def foo - y = StaticArray(Int8, 512).new(0) + {% if flag?(:netbsd) %} + # FIXME: on netbsd the process crashes with SIGILL after receiving SIGSEGV + pending "detects stack overflow on the main stack" + pending "detects stack overflow on a fiber stack" + {% else %} + it "detects stack overflow on the main stack", tags: %w[slow] do + # This spec can take some time under FreeBSD where + # the default stack size is 0.5G. Setting a + # smaller stack size with `ulimit -s 8192` + # will address this. + status, _, error = compile_and_run_source <<-'CRYSTAL' + def foo + y = StaticArray(Int8, 512).new(0) + foo + end foo - end - foo - CRYSTAL + CRYSTAL - status.success?.should be_false - error.should contain("Stack overflow") - end + status.success?.should be_false + error.should contain("Stack overflow") + end - it "detects stack overflow on a fiber stack", tags: %w[slow] do - status, _, error = compile_and_run_source <<-'CRYSTAL' - def foo - y = StaticArray(Int8, 512).new(0) - foo - end + it "detects stack overflow on a fiber stack", tags: %w[slow] do + status, _, error = compile_and_run_source <<-'CRYSTAL' + def foo + y = StaticArray(Int8, 512).new(0) + foo + end - spawn do - foo - end + spawn do + foo + end - sleep 60.seconds - CRYSTAL + sleep 60.seconds + CRYSTAL - status.success?.should be_false - error.should contain("Stack overflow") - end + status.success?.should be_false + error.should contain("Stack overflow") + end + {% end %} end diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index 0c6113a4a7ff..ee3c861956b8 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -96,7 +96,7 @@ describe TCPServer, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) %} + {% elsif flag?(:android) || flag?(:netbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error @@ -110,7 +110,7 @@ describe TCPServer, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) %} + {% elsif flag?(:android) || flag?(:netbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index f3d460f92401..5ec3467362e0 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -79,7 +79,7 @@ describe TCPSocket, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) %} + {% elsif flag?(:android) || flag?(:netbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error @@ -93,7 +93,7 @@ describe TCPSocket, tags: "network" do # FIXME: Resolve special handling for win32. The error code handling should be identical. {% if flag?(:win32) %} [WinError::WSAHOST_NOT_FOUND, WinError::WSATRY_AGAIN].should contain err.os_error - {% elsif flag?(:android) %} + {% elsif flag?(:android) || flag?(:netbsd) %} err.os_error.should eq(Errno.new(LibC::EAI_NODATA)) {% else %} [Errno.new(LibC::EAI_NONAME), Errno.new(LibC::EAI_AGAIN)].should contain err.os_error @@ -142,7 +142,7 @@ describe TCPSocket, tags: "network" do (client.tcp_nodelay = false).should be_false client.tcp_nodelay?.should be_false - {% unless flag?(:openbsd) %} + {% unless flag?(:openbsd) || flag?(:netbsd) %} (client.tcp_keepalive_idle = 42).should eq 42 client.tcp_keepalive_idle.should eq 42 (client.tcp_keepalive_interval = 42).should eq 42 diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index 6e4b607b80ea..9b624110fad9 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -85,6 +85,9 @@ describe UDPSocket, tags: "network" do elsif {{ flag?(:freebsd) }} && family == Socket::Family::INET6 # FIXME: fails with "Error sending datagram to [ipv6]:port: Network is unreachable" pending "joins and transmits to multicast groups" + elsif {{ flag?(:netbsd) }} && family == Socket::Family::INET6 + # FIXME: fails with "setsockopt: EADDRNOTAVAIL" + pending "joins and transmits to multicast groups" else it "joins and transmits to multicast groups" do udp = UDPSocket.new(family) diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index 2ffe5bf3d1fa..6d7487ded0e2 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2830,7 +2830,7 @@ describe "String" do bytes.to_a.should eq([72, 0, 101, 0, 108, 0, 108, 0, 111, 0]) end - {% unless flag?(:musl) || flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) %} + {% unless flag?(:musl) || flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "flushes the shift state (#11992)" do "\u{00CA}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{00CA}\u{0304}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) @@ -2839,7 +2839,7 @@ describe "String" do # FreeBSD iconv encoder expects ISO/IEC 10646 compatibility code points, # see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details. - {% if flag?(:freebsd) || flag?(:dragonfly) %} + {% if flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "flushes the shift state (#11992)" do "\u{F329}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{F325}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) @@ -2883,7 +2883,7 @@ describe "String" do String.new(bytes, "UTF-16LE").should eq("Hello") end - {% unless flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) %} + {% unless flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{00CA}\u{0304}") @@ -2892,7 +2892,7 @@ describe "String" do # FreeBSD iconv decoder returns ISO/IEC 10646-1:2000 code points, # see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details. - {% if flag?(:freebsd) || flag?(:dragonfly) %} + {% if flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{F325}") diff --git a/src/compiler/crystal/semantic/bindings.cr b/src/compiler/crystal/semantic/bindings.cr index c5fe9f164742..a7dacb8668c9 100644 --- a/src/compiler/crystal/semantic/bindings.cr +++ b/src/compiler/crystal/semantic/bindings.cr @@ -1,7 +1,77 @@ module Crystal + # Specialized container for ASTNodes to use for bindings tracking. + # + # The average number of elements in both dependencies and observers is below 2 + # for ASTNodes. This struct inlines the first two elements saving up 4 + # allocations per node (two arrays, with a header and buffer for each) but we + # need to pay a slight extra cost in memory upfront: a total of 6 pointers (48 + # bytes) vs 2 pointers (16 bytes). The other downside is that since this is a + # struct, we need to be careful with mutation. + struct SmallNodeList + include Enumerable(ASTNode) + + @first : ASTNode? + @second : ASTNode? + @tail : Array(ASTNode)? + + def each(& : ASTNode ->) + yield @first || return + yield @second || return + @tail.try(&.each { |node| yield node }) + end + + def size + if @first.nil? + 0 + elsif @second.nil? + 1 + elsif (tail = @tail).nil? + 2 + else + 2 + tail.size + end + end + + def push(node : ASTNode) : self + if @first.nil? + @first = node + elsif @second.nil? + @second = node + elsif (tail = @tail).nil? + @tail = [node] of ASTNode + else + tail.push(node) + end + self + end + + def reject!(& : ASTNode ->) : self + if first = @first + if second = @second + if tail = @tail + tail.reject! { |node| yield node } + end + if yield second + @second = tail.try &.shift? + end + end + if yield first + @first = @second + @second = tail.try &.shift? + end + end + self + end + + def concat(nodes : Enumerable(ASTNode)) : self + nodes.each { |node| self.push(node) } + self + end + end + class ASTNode - property! dependencies : Array(ASTNode) - property observers : Array(ASTNode)? + getter dependencies : SmallNodeList = SmallNodeList.new + @observers : SmallNodeList = SmallNodeList.new property enclosing_call : Call? @dirty = false @@ -107,8 +177,8 @@ module Crystal end def bind_to(node : ASTNode) : Nil - bind(node) do |dependencies| - dependencies.push node + bind(node) do + @dependencies.push node node.add_observer self end end @@ -116,8 +186,8 @@ module Crystal def bind_to(nodes : Indexable) : Nil return if nodes.empty? - bind do |dependencies| - dependencies.concat nodes + bind do + @dependencies.concat nodes nodes.each &.add_observer self end end @@ -130,9 +200,7 @@ module Crystal raise_frozen_type freeze_type, from_type, from end - dependencies = @dependencies ||= [] of ASTNode - - yield dependencies + yield new_type = type_from_dependencies new_type = map_type(new_type) if new_type @@ -150,7 +218,7 @@ module Crystal end def type_from_dependencies : Type? - Type.merge dependencies + Type.merge @dependencies end def unbind_from(nodes : Nil) @@ -158,18 +226,17 @@ module Crystal end def unbind_from(node : ASTNode) - @dependencies.try &.reject! &.same?(node) + @dependencies.reject! &.same?(node) node.remove_observer self end - def unbind_from(nodes : Array(ASTNode)) - @dependencies.try &.reject! { |dep| nodes.any? &.same?(dep) } + def unbind_from(nodes : Enumerable(ASTNode)) + @dependencies.reject! { |dep| nodes.any? &.same?(dep) } nodes.each &.remove_observer self end def add_observer(observer) - observers = @observers ||= [] of ASTNode - observers.push observer + @observers.push observer end def remove_observer(observer) @@ -269,16 +336,10 @@ module Crystal visited = Set(ASTNode).new.compare_by_identity owner_trace << node if node.type?.try &.includes_type?(owner) visited.add node - while deps = node.dependencies? - dependencies = deps.select { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) } - if dependencies.size > 0 - node = dependencies.first - nil_reason = node.nil_reason if node.is_a?(MetaTypeVar) - owner_trace << node if node - visited.add node - else - break - end + while node = node.dependencies.find { |dep| dep.type? && dep.type.includes_type?(owner) && !visited.includes?(dep) } + nil_reason = node.nil_reason if node.is_a?(MetaTypeVar) + owner_trace << node if node + visited.add node end MethodTraceException.new(owner, owner_trace, nil_reason, program.show_error_trace?) diff --git a/src/compiler/crystal/semantic/call_error.cr b/src/compiler/crystal/semantic/call_error.cr index aee5b9e2019b..d19be20afbad 100644 --- a/src/compiler/crystal/semantic/call_error.cr +++ b/src/compiler/crystal/semantic/call_error.cr @@ -643,8 +643,7 @@ class Crystal::Call if obj.is_a?(InstanceVar) scope = self.scope ivar = scope.lookup_instance_var(obj.name) - deps = ivar.dependencies? - if deps && deps.size == 1 && deps.first.same?(program.nil_var) + if ivar.dependencies.size == 1 && ivar.dependencies.first.same?(program.nil_var) similar_name = scope.lookup_similar_instance_var_name(ivar.name) if similar_name msg << colorize(" (#{ivar.name} was never assigned a value, did you mean #{similar_name}?)").yellow.bold diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 541e0f51d662..054c7871bd8e 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -1090,10 +1090,7 @@ module Crystal node = super unless node.type? - if dependencies = node.dependencies? - node.unbind_from node.dependencies - end - + node.unbind_from node.dependencies node.bind_to node.expressions end diff --git a/src/compiler/crystal/semantic/filters.cr b/src/compiler/crystal/semantic/filters.cr index 66d1a728804b..7dd253fc2292 100644 --- a/src/compiler/crystal/semantic/filters.cr +++ b/src/compiler/crystal/semantic/filters.cr @@ -1,7 +1,7 @@ module Crystal class TypeFilteredNode < ASTNode def initialize(@filter : TypeFilter, @node : ASTNode) - @dependencies = [@node] of ASTNode + @dependencies.push @node node.add_observer self update(@node) end diff --git a/src/compiler/crystal/semantic/main_visitor.cr b/src/compiler/crystal/semantic/main_visitor.cr index 905d5bac8cb1..efd76f76f056 100644 --- a/src/compiler/crystal/semantic/main_visitor.cr +++ b/src/compiler/crystal/semantic/main_visitor.cr @@ -373,7 +373,7 @@ module Crystal var.bind_to(@program.nil_var) var.nil_if_read = false - meta_var.bind_to(@program.nil_var) unless meta_var.dependencies.try &.any? &.same?(@program.nil_var) + meta_var.bind_to(@program.nil_var) unless meta_var.dependencies.any? &.same?(@program.nil_var) node.bind_to(@program.nil_var) end @@ -1283,7 +1283,7 @@ module Crystal # It can happen that this call is inside an ArrayLiteral or HashLiteral, # was expanded but isn't bound to the expansion because the call (together # with its expansion) was cloned. - if (expanded = node.expanded) && (!node.dependencies? || !node.type?) + if (expanded = node.expanded) && (node.dependencies.empty? || !node.type?) node.bind_to(expanded) end diff --git a/src/compiler/crystal/semantic/type_merge.cr b/src/compiler/crystal/semantic/type_merge.cr index 874949dd516d..67e9f1b61911 100644 --- a/src/compiler/crystal/semantic/type_merge.cr +++ b/src/compiler/crystal/semantic/type_merge.cr @@ -17,7 +17,7 @@ module Crystal end end - def type_merge(nodes : Array(ASTNode)) : Type? + def type_merge(nodes : Enumerable(ASTNode)) : Type? case nodes.size when 0 nil @@ -25,8 +25,10 @@ module Crystal nodes.first.type? when 2 # Merging two types is the most common case, so we optimize it - first, second = nodes - type_merge(first.type?, second.type?) + # We use `#each_cons_pair` to avoid any intermediate allocation + nodes.each_cons_pair do |first, second| + return type_merge(first.type?, second.type?) + end else combined_union_of compact_types(nodes, &.type?) end @@ -161,7 +163,7 @@ module Crystal end class Type - def self.merge(nodes : Array(ASTNode)) : Type? + def self.merge(nodes : Enumerable(ASTNode)) : Type? nodes.find(&.type?).try &.type.program.type_merge(nodes) end diff --git a/src/crystal/pe.cr b/src/crystal/pe.cr new file mode 100644 index 000000000000..d1b19401ad19 --- /dev/null +++ b/src/crystal/pe.cr @@ -0,0 +1,110 @@ +module Crystal + # :nodoc: + # + # Portable Executable reader. + # + # Documentation: + # - + struct PE + class Error < Exception + end + + record SectionHeader, name : String, virtual_offset : UInt32, offset : UInt32, size : UInt32 + + record COFFSymbol, offset : UInt32, name : String + + # addresses in COFF debug info are relative to this image base; used by + # `Exception::CallStack.read_dwarf_sections` to calculate the real relocated + # addresses + getter original_image_base : UInt64 + + @section_headers : Slice(SectionHeader) + @string_table_base : UInt32 + + # mapping from zero-based section index to list of symbols sorted by + # offsets within that section + getter coff_symbols = Hash(Int32, Array(COFFSymbol)).new + + def self.open(path : String | ::Path, &) + File.open(path, "r") do |file| + yield new(file) + end + end + + def initialize(@io : IO::FileDescriptor) + dos_header = uninitialized LibC::IMAGE_DOS_HEADER + io.read_fully(pointerof(dos_header).to_slice(1).to_unsafe_bytes) + raise Error.new("Invalid DOS header") unless dos_header.e_magic == 0x5A4D # MZ + + io.seek(dos_header.e_lfanew) + nt_header = uninitialized LibC::IMAGE_NT_HEADERS + io.read_fully(pointerof(nt_header).to_slice(1).to_unsafe_bytes) + raise Error.new("Invalid PE header") unless nt_header.signature == 0x00004550 # PE\0\0 + + @original_image_base = nt_header.optionalHeader.imageBase + @string_table_base = nt_header.fileHeader.pointerToSymbolTable + nt_header.fileHeader.numberOfSymbols * sizeof(LibC::IMAGE_SYMBOL) + + section_count = nt_header.fileHeader.numberOfSections + nt_section_headers = Pointer(LibC::IMAGE_SECTION_HEADER).malloc(section_count).to_slice(section_count) + io.read_fully(nt_section_headers.to_unsafe_bytes) + + @section_headers = nt_section_headers.map do |nt_header| + if nt_header.name[0] === '/' + # section name is longer than 8 bytes; look up the COFF string table + name_buf = nt_header.name.to_slice + 1 + string_offset = String.new(name_buf.to_unsafe, name_buf.index(0) || name_buf.size).to_i + io.seek(@string_table_base + string_offset) + name = io.gets('\0', chomp: true).not_nil! + else + name = String.new(nt_header.name.to_unsafe, nt_header.name.index(0) || nt_header.name.size) + end + + SectionHeader.new(name: name, virtual_offset: nt_header.virtualAddress, offset: nt_header.pointerToRawData, size: nt_header.virtualSize) + end + + io.seek(nt_header.fileHeader.pointerToSymbolTable) + image_symbol_count = nt_header.fileHeader.numberOfSymbols + image_symbols = Pointer(LibC::IMAGE_SYMBOL).malloc(image_symbol_count).to_slice(image_symbol_count) + io.read_fully(image_symbols.to_unsafe_bytes) + + aux_count = 0 + image_symbols.each_with_index do |sym, i| + if aux_count == 0 + aux_count = sym.numberOfAuxSymbols.to_i + else + aux_count &-= 1 + end + + next unless aux_count == 0 + next unless sym.type.bits_set?(0x20) # COFF function + next unless sym.sectionNumber > 0 # one-based section index + next unless sym.storageClass.in?(LibC::IMAGE_SYM_CLASS_EXTERNAL, LibC::IMAGE_SYM_CLASS_STATIC) + + if sym.n.name.short == 0 + io.seek(@string_table_base + sym.n.name.long) + name = io.gets('\0', chomp: true).not_nil! + else + name = String.new(sym.n.shortName.to_slice).rstrip('\0') + end + + # `@coff_symbols` uses zero-based indices + section_coff_symbols = @coff_symbols.put_if_absent(sym.sectionNumber.to_i &- 1) { [] of COFFSymbol } + section_coff_symbols << COFFSymbol.new(sym.value, name) + end + + # add one sentinel symbol to ensure binary search on the offsets works + @coff_symbols.each_with_index do |(_, symbols), i| + symbols.sort_by!(&.offset) + symbols << COFFSymbol.new(@section_headers[i].size, "??") + end + end + + def read_section?(name : String, &) + if sh = @section_headers.find(&.name.== name) + @io.seek(sh.offset) do + yield sh, @io + end + end + end + end +end diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr index 5e66b33b65e7..72d1183dcc72 100644 --- a/src/crystal/system/unix/dir.cr +++ b/src/crystal/system/unix/dir.cr @@ -42,7 +42,12 @@ module Crystal::System::Dir end def self.info(dir, path) : ::File::Info - Crystal::System::FileDescriptor.system_info LibC.dirfd(dir) + fd = {% if flag?(:netbsd) %} + dir.value.dd_fd + {% else %} + LibC.dirfd(dir) + {% end %} + Crystal::System::FileDescriptor.system_info(fd) end def self.close(dir, path) : Nil diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 7b7b443ce310..b6f9cf2b7ccd 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -116,6 +116,7 @@ module Crystal::System::File WinError::ERROR_FILE_NOT_FOUND, WinError::ERROR_PATH_NOT_FOUND, WinError::ERROR_INVALID_NAME, + WinError::ERROR_DIRECTORY, } def self.check_not_found_error(message, path) diff --git a/src/crystal/system/win32/signal.cr b/src/crystal/system/win32/signal.cr index d805ea4fd1ab..4cebe7cf9c6a 100644 --- a/src/crystal/system/win32/signal.cr +++ b/src/crystal/system/win32/signal.cr @@ -1,4 +1,5 @@ require "c/signal" +require "c/malloc" module Crystal::System::Signal def self.trap(signal, handler) : Nil @@ -16,4 +17,47 @@ module Crystal::System::Signal def self.ignore(signal) : Nil raise NotImplementedError.new("Crystal::System::Signal.ignore") end + + def self.setup_seh_handler + LibC.AddVectoredExceptionHandler(1, ->(exception_info) do + case exception_info.value.exceptionRecord.value.exceptionCode + when LibC::EXCEPTION_ACCESS_VIOLATION + addr = exception_info.value.exceptionRecord.value.exceptionInformation[1] + Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr) + {% if flag?(:gnu) %} + Exception::CallStack.print_backtrace + {% else %} + Exception::CallStack.print_backtrace(exception_info) + {% end %} + LibC._exit(1) + when LibC::EXCEPTION_STACK_OVERFLOW + LibC._resetstkoflw + Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n" + {% if flag?(:gnu) %} + Exception::CallStack.print_backtrace + {% else %} + Exception::CallStack.print_backtrace(exception_info) + {% end %} + LibC._exit(1) + else + LibC::EXCEPTION_CONTINUE_SEARCH + end + end) + + # ensure that even in the case of stack overflow there is enough reserved + # stack space for recovery (for other threads this is done in + # `Crystal::System::Thread.thread_proc`) + stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE + LibC.SetThreadStackGuarantee(pointerof(stack_size)) + + # this catches invalid argument checks inside the C runtime library + LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do + message = expression ? String.from_utf16(expression)[0] : "(no message)" + Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message + caller.each do |frame| + Crystal::System.print_error " from %s\n", frame + end + LibC._exit(1) + end) + end end diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr index 44a281570c1c..506317d2580e 100644 --- a/src/exception/call_stack.cr +++ b/src/exception/call_stack.cr @@ -1,10 +1,7 @@ {% if flag?(:interpreted) %} require "./call_stack/interpreter" -{% elsif flag?(:win32) %} +{% elsif flag?(:win32) && !flag?(:gnu) %} require "./call_stack/stackwalk" - {% if flag?(:gnu) %} - require "./lib_unwind" - {% end %} {% elsif flag?(:wasm32) %} require "./call_stack/null" {% else %} diff --git a/src/exception/call_stack/dwarf.cr b/src/exception/call_stack/dwarf.cr index 96d99f03205a..253a72a38ebc 100644 --- a/src/exception/call_stack/dwarf.cr +++ b/src/exception/call_stack/dwarf.cr @@ -10,6 +10,10 @@ struct Exception::CallStack @@dwarf_line_numbers : Crystal::DWARF::LineNumbers? @@dwarf_function_names : Array(Tuple(LibC::SizeT, LibC::SizeT, String))? + {% if flag?(:win32) %} + @@coff_symbols : Hash(Int32, Array(Crystal::PE::COFFSymbol))? + {% end %} + # :nodoc: def self.load_debug_info : Nil return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0" diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr index efa54f41329c..51d565528577 100644 --- a/src/exception/call_stack/elf.cr +++ b/src/exception/call_stack/elf.cr @@ -1,65 +1,83 @@ -require "crystal/elf" -{% unless flag?(:wasm32) %} - require "c/link" +{% if flag?(:win32) %} + require "crystal/pe" +{% else %} + require "crystal/elf" + {% unless flag?(:wasm32) %} + require "c/link" + {% end %} {% end %} struct Exception::CallStack - private struct DlPhdrData - getter program : String - property base_address : LibC::Elf_Addr = 0 + {% unless flag?(:win32) %} + private struct DlPhdrData + getter program : String + property base_address : LibC::Elf_Addr = 0 - def initialize(@program : String) + def initialize(@program : String) + end end - end + {% end %} protected def self.load_debug_info_impl : Nil program = Process.executable_path return unless program && File::Info.readable? program - data = DlPhdrData.new(program) - - phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| - # `dl_iterate_phdr` does not always visit the current program first; on - # Android the first object is `/system/bin/linker64`, the second is the - # full program path (not the empty string), so we check both here - name_c_str = info.value.name - if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0) - # The first entry is the header for the current program. - # Note that we avoid allocating here and just store the base address - # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns. - # Calling self.read_dwarf_sections from this callback may lead to reallocations - # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084). - data.as(DlPhdrData*).value.base_address = info.value.addr - 1 - else - 0 + + {% if flag?(:win32) %} + if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule) != 0 + self.read_dwarf_sections(program, hmodule.address) end - end + {% else %} + data = DlPhdrData.new(program) - LibC.dl_iterate_phdr(phdr_callback, pointerof(data)) - self.read_dwarf_sections(data.program, data.base_address) + phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| + # `dl_iterate_phdr` does not always visit the current program first; on + # Android the first object is `/system/bin/linker64`, the second is the + # full program path (not the empty string), so we check both here + name_c_str = info.value.name + if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0) + # The first entry is the header for the current program. + # Note that we avoid allocating here and just store the base address + # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns. + # Calling self.read_dwarf_sections from this callback may lead to reallocations + # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084). + data.as(DlPhdrData*).value.base_address = info.value.addr + 1 + else + 0 + end + end + + LibC.dl_iterate_phdr(phdr_callback, pointerof(data)) + self.read_dwarf_sections(data.program, data.base_address) + {% end %} end protected def self.read_dwarf_sections(program, base_address = 0) - Crystal::ELF.open(program) do |elf| - line_strings = elf.read_section?(".debug_line_str") do |sh, io| + {{ flag?(:win32) ? Crystal::PE : Crystal::ELF }}.open(program) do |image| + {% if flag?(:win32) %} + base_address -= image.original_image_base + @@coff_symbols = image.coff_symbols + {% end %} + + line_strings = image.read_section?(".debug_line_str") do |sh, io| Crystal::DWARF::Strings.new(io, sh.offset, sh.size) end - strings = elf.read_section?(".debug_str") do |sh, io| + strings = image.read_section?(".debug_str") do |sh, io| Crystal::DWARF::Strings.new(io, sh.offset, sh.size) end - elf.read_section?(".debug_line") do |sh, io| + image.read_section?(".debug_line") do |sh, io| @@dwarf_line_numbers = Crystal::DWARF::LineNumbers.new(io, sh.size, base_address, strings, line_strings) end - elf.read_section?(".debug_info") do |sh, io| + image.read_section?(".debug_info") do |sh, io| names = [] of {LibC::SizeT, LibC::SizeT, String} while (offset = io.pos - sh.offset) < sh.size info = Crystal::DWARF::Info.new(io, offset) - elf.read_section?(".debug_abbrev") do |sh, io| + image.read_section?(".debug_abbrev") do |sh, io| info.read_abbreviations(io) end diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 1542d52cc736..c0f75867aeba 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -1,9 +1,11 @@ -require "c/dlfcn" +{% unless flag?(:win32) %} + require "c/dlfcn" +{% end %} require "c/stdio" require "c/string" require "../lib_unwind" -{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) %} +{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) || flag?(:win32) %} require "./dwarf" {% else %} require "./null" @@ -33,7 +35,11 @@ struct Exception::CallStack {% end %} def self.setup_crash_handler - Crystal::System::Signal.setup_segfault_handler + {% if flag?(:win32) %} + Crystal::System::Signal.setup_seh_handler + {% else %} + Crystal::System::Signal.setup_segfault_handler + {% end %} end {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %} @@ -167,9 +173,102 @@ struct Exception::CallStack end end - private def self.dladdr(ip, &) - if LibC.dladdr(ip, out info) != 0 - yield info.dli_fname, info.dli_sname, info.dli_saddr + {% if flag?(:win32) %} + def self.dladdr(ip, &) + if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | LibC::GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, ip.as(LibC::LPWSTR), out hmodule) != 0 + symbol, address = internal_symbol(hmodule, ip) || external_symbol(hmodule, ip) || return + + utf16_file = uninitialized LibC::WCHAR[LibC::MAX_PATH] + len = LibC.GetModuleFileNameW(hmodule, utf16_file, utf16_file.size) + if 0 < len < utf16_file.size + utf8_file = uninitialized UInt8[sizeof(UInt8[LibC::MAX_PATH][3])] + file = utf8_file.to_unsafe + appender = file.appender + String.each_utf16_char(utf16_file.to_slice[0, len + 1]) do |ch| + ch.each_byte { |b| appender << b } + end + else + file = Pointer(UInt8).null + end + + yield file, symbol, address + end end - end + + private def self.internal_symbol(hmodule, ip) + if coff_symbols = @@coff_symbols + if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out this_hmodule) != 0 && this_hmodule == hmodule + section_base, section_index = lookup_section(hmodule, ip) || return + offset = ip - section_base + section_coff_symbols = coff_symbols[section_index]? || return + next_sym = section_coff_symbols.bsearch_index { |sym| offset < sym.offset } || return + sym = section_coff_symbols[next_sym - 1]? || return + + {sym.name.to_unsafe, section_base + sym.offset} + end + end + end + + private def self.external_symbol(hmodule, ip) + if dir = data_directory(hmodule, LibC::IMAGE_DIRECTORY_ENTRY_EXPORT) + exports = dir.to_unsafe.as(LibC::IMAGE_EXPORT_DIRECTORY*).value + + found_address = Pointer(Void).null + found_index = -1 + + func_address_offsets = (hmodule + exports.addressOfFunctions).as(LibC::DWORD*).to_slice(exports.numberOfFunctions) + func_address_offsets.each_with_index do |offset, i| + address = hmodule + offset + if found_address < address <= ip + found_address, found_index = address, i + end + end + + return unless found_address + + func_name_ordinals = (hmodule + exports.addressOfNameOrdinals).as(LibC::WORD*).to_slice(exports.numberOfNames) + if ordinal_index = func_name_ordinals.index(&.== found_index) + symbol = (hmodule + (hmodule + exports.addressOfNames).as(LibC::DWORD*)[ordinal_index]).as(UInt8*) + {symbol, found_address} + end + end + end + + private def self.lookup_section(hmodule, ip) + dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*) + return unless dos_header.value.e_magic == 0x5A4D # MZ + + nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*) + return unless nt_header.value.signature == 0x00004550 # PE\0\0 + + section_headers = (nt_header + 1).as(LibC::IMAGE_SECTION_HEADER*).to_slice(nt_header.value.fileHeader.numberOfSections) + section_headers.each_with_index do |header, i| + base = hmodule + header.virtualAddress + if base <= ip < base + header.virtualSize + return base, i + end + end + end + + private def self.data_directory(hmodule, index) + dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*) + return unless dos_header.value.e_magic == 0x5A4D # MZ + + nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*) + return unless nt_header.value.signature == 0x00004550 # PE\0\0 + return unless nt_header.value.optionalHeader.magic == {{ flag?(:bits64) ? 0x20b : 0x10b }} + return unless index.in?(0...{16, nt_header.value.optionalHeader.numberOfRvaAndSizes}.min) + + directory = nt_header.value.optionalHeader.dataDirectory.to_unsafe[index] + if directory.virtualAddress != 0 + Bytes.new(hmodule.as(UInt8*) + directory.virtualAddress, directory.size, read_only: true) + end + end + {% else %} + private def self.dladdr(ip, &) + if LibC.dladdr(ip, out info) != 0 + yield info.dli_fname, info.dli_sname, info.dli_saddr + end + end + {% end %} end diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr index 6ac59fa6db48..d7e3da8e35f1 100644 --- a/src/exception/call_stack/stackwalk.cr +++ b/src/exception/call_stack/stackwalk.cr @@ -1,5 +1,4 @@ require "c/dbghelp" -require "c/malloc" # :nodoc: struct Exception::CallStack @@ -33,38 +32,7 @@ struct Exception::CallStack end def self.setup_crash_handler - LibC.AddVectoredExceptionHandler(1, ->(exception_info) do - case exception_info.value.exceptionRecord.value.exceptionCode - when LibC::EXCEPTION_ACCESS_VIOLATION - addr = exception_info.value.exceptionRecord.value.exceptionInformation[1] - Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr) - print_backtrace(exception_info) - LibC._exit(1) - when LibC::EXCEPTION_STACK_OVERFLOW - LibC._resetstkoflw - Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n" - print_backtrace(exception_info) - LibC._exit(1) - else - LibC::EXCEPTION_CONTINUE_SEARCH - end - end) - - # ensure that even in the case of stack overflow there is enough reserved - # stack space for recovery (for other threads this is done in - # `Crystal::System::Thread.thread_proc`) - stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE - LibC.SetThreadStackGuarantee(pointerof(stack_size)) - - # this catches invalid argument checks inside the C runtime library - LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do - message = expression ? String.from_utf16(expression)[0] : "(no message)" - Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message - caller.each do |frame| - Crystal::System.print_error " from %s\n", frame - end - LibC._exit(1) - end) + Crystal::System::Signal.setup_seh_handler end {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %} @@ -168,33 +136,6 @@ 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) diff --git a/src/lib_c/x86_64-netbsd/c/dirent.cr b/src/lib_c/x86_64-netbsd/c/dirent.cr index 71dabe7b08ce..e3b8492083f7 100644 --- a/src/lib_c/x86_64-netbsd/c/dirent.cr +++ b/src/lib_c/x86_64-netbsd/c/dirent.cr @@ -29,5 +29,4 @@ lib LibC fun opendir = __opendir30(x0 : Char*) : DIR* fun readdir = __readdir30(x0 : DIR*) : Dirent* fun rewinddir(x0 : DIR*) : Void - fun dirfd(dirp : DIR*) : Int end diff --git a/src/lib_c/x86_64-netbsd/c/netdb.cr b/src/lib_c/x86_64-netbsd/c/netdb.cr index 4443325cd487..c098ab2f5fc6 100644 --- a/src/lib_c/x86_64-netbsd/c/netdb.cr +++ b/src/lib_c/x86_64-netbsd/c/netdb.cr @@ -13,6 +13,7 @@ lib LibC EAI_FAIL = 4 EAI_FAMILY = 5 EAI_MEMORY = 6 + EAI_NODATA = 7 EAI_NONAME = 8 EAI_SERVICE = 9 EAI_SOCKTYPE = 10 diff --git a/src/lib_c/x86_64-netbsd/c/sys/time.cr b/src/lib_c/x86_64-netbsd/c/sys/time.cr index f276784708c0..3bb54d42c5cd 100644 --- a/src/lib_c/x86_64-netbsd/c/sys/time.cr +++ b/src/lib_c/x86_64-netbsd/c/sys/time.cr @@ -13,5 +13,5 @@ lib LibC fun gettimeofday = __gettimeofday50(x0 : Timeval*, x1 : Timezone*) : Int fun utimes = __utimes50(path : Char*, times : Timeval[2]) : Int - fun futimens = __futimens50(fd : Int, times : Timespec[2]) : Int + fun futimens(fd : Int, times : Timespec[2]) : Int end diff --git a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr index 37a95f3fa089..5612233553d9 100644 --- a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr @@ -9,6 +9,9 @@ lib LibC fun LoadLibraryExW(lpLibFileName : LPWSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE fun FreeLibrary(hLibModule : HMODULE) : BOOL + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002 + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004 + fun GetModuleHandleExW(dwFlags : DWORD, lpModuleName : LPWSTR, phModule : HMODULE*) : BOOL fun GetProcAddress(hModule : HMODULE, lpProcName : LPSTR) : FARPROC diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr index 02240af1e0e5..99c8f24ac9e1 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -392,6 +392,10 @@ lib LibC optionalHeader : IMAGE_OPTIONAL_HEADER64 end + IMAGE_DIRECTORY_ENTRY_EXPORT = 0 + IMAGE_DIRECTORY_ENTRY_IMPORT = 1 + IMAGE_DIRECTORY_ENTRY_IAT = 12 + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 struct IMAGE_SECTION_HEADER @@ -407,11 +411,48 @@ lib LibC characteristics : DWORD end + struct IMAGE_EXPORT_DIRECTORY + characteristics : DWORD + timeDateStamp : DWORD + majorVersion : WORD + minorVersion : WORD + name : DWORD + base : DWORD + numberOfFunctions : DWORD + numberOfNames : DWORD + addressOfFunctions : DWORD + addressOfNames : DWORD + addressOfNameOrdinals : DWORD + end + struct IMAGE_IMPORT_BY_NAME hint : WORD name : CHAR[1] end + struct IMAGE_SYMBOL_n_name + short : DWORD + long : DWORD + end + + union IMAGE_SYMBOL_n + shortName : BYTE[8] + name : IMAGE_SYMBOL_n_name + end + + IMAGE_SYM_CLASS_EXTERNAL = 2 + IMAGE_SYM_CLASS_STATIC = 3 + + @[Packed] + struct IMAGE_SYMBOL + n : IMAGE_SYMBOL_n + value : DWORD + sectionNumber : Short + type : WORD + storageClass : BYTE + numberOfAuxSymbols : BYTE + end + union IMAGE_THUNK_DATA64_u1 forwarderString : ULongLong function : ULongLong diff --git a/src/openssl/lib_crypto.cr b/src/openssl/lib_crypto.cr index 8d450b28ff17..fecc69ad44fc 100644 --- a/src/openssl/lib_crypto.cr +++ b/src/openssl/lib_crypto.cr @@ -1,6 +1,6 @@ {% begin %} lib LibCrypto - {% if flag?(:win32) %} + {% if flag?(:msvc) %} {% from_libressl = false %} {% ssl_version = nil %} {% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %} @@ -13,10 +13,12 @@ {% end %} {% ssl_version ||= "0.0.0" %} {% else %} - {% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") && - (`test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false` != "false") && - (`printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %} - {% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %} + # these have to be wrapped in `sh -c` since for MinGW-w64 the compiler + # passes the command string to `LibC.CreateProcessW` + {% from_libressl = (`sh -c 'hash pkg-config 2> /dev/null || printf %s false'` != "false") && + (`sh -c 'test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false'` != "false") && + (`sh -c 'printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -'`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %} + {% ssl_version = `sh -c 'hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0'`.split.last.gsub(/[^0-9.]/, "") %} {% end %} {% if from_libressl %} diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr index 6adb3f172a3b..4e7e2def549c 100644 --- a/src/openssl/lib_ssl.cr +++ b/src/openssl/lib_ssl.cr @@ -6,7 +6,7 @@ require "./lib_crypto" {% begin %} lib LibSSL - {% if flag?(:win32) %} + {% if flag?(:msvc) %} {% from_libressl = false %} {% ssl_version = nil %} {% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %} @@ -19,10 +19,12 @@ require "./lib_crypto" {% end %} {% ssl_version ||= "0.0.0" %} {% else %} - {% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") && - (`test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false` != "false") && - (`printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %} - {% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %} + # these have to be wrapped in `sh -c` since for MinGW-w64 the compiler + # passes the command string to `LibC.CreateProcessW` + {% from_libressl = (`sh -c 'hash pkg-config 2> /dev/null || printf %s false'` != "false") && + (`sh -c 'test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false'` != "false") && + (`sh -c 'printf "#include \nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -'`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %} + {% ssl_version = `sh -c 'hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0'`.split.last.gsub(/[^0-9.]/, "") %} {% end %} {% if from_libressl %} diff --git a/src/raise.cr b/src/raise.cr index a8e06a3c3930..0c9563495a94 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -181,7 +181,7 @@ end 0u64 end {% else %} - {% mingw = flag?(:windows) && flag?(:gnu) %} + {% mingw = flag?(:win32) && 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