Skip to content

Commit

Permalink
Enable the interpreter on Windows (#14964)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil authored Sep 7, 2024
1 parent 136f85e commit cdd9ccf
Show file tree
Hide file tree
Showing 17 changed files with 163 additions and 27 deletions.
37 changes: 36 additions & 1 deletion .github/workflows/win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}

env:
SPEC_SPLIT_DOTS: 160
CI_LLVM_VERSION: "18.1.1"

jobs:
Expand Down Expand Up @@ -266,13 +267,47 @@ jobs:
run: make -f Makefile.win samples

x86_64-windows-release:
if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/'))
needs: [x86_64-windows-libs, x86_64-windows-dlls, x86_64-windows-llvm-libs, x86_64-windows-llvm-dlls]
uses: ./.github/workflows/win_build_portable.yml
with:
release: true
llvm_version: "18.1.1"

x86_64-windows-test-interpreter:
runs-on: windows-2022
needs: [x86_64-windows-release]
steps:
- name: Disable CRLF line ending substitution
run: |
git config --global core.autocrlf false
- name: Download Crystal source
uses: actions/checkout@v4

- name: Download Crystal executable
uses: actions/download-artifact@v4
with:
name: crystal-release
path: build

- name: Restore LLVM
uses: actions/cache/restore@v4
with:
path: llvm
key: llvm-libs-${{ env.CI_LLVM_VERSION }}-msvc
fail-on-cache-miss: true

- name: Set up environment
run: |
Add-Content $env:GITHUB_PATH "$(pwd)\build"
Add-Content $env:GITHUB_ENV "CRYSTAL_SPEC_COMPILER_BIN=$(pwd)\build\crystal.exe"
- name: Run stdlib specs with interpreter
run: bin\crystal i spec\std_spec.cr

- name: Run primitives specs with interpreter
run: bin\crystal i spec\primitives_spec.cr

x86_64-windows-installer:
if: github.repository_owner == 'crystal-lang' && (startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/ci/'))
runs-on: windows-2022
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/win_build_portable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ jobs:
- name: Build Crystal
run: |
bin/crystal.bat env
make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }}
make -f Makefile.win -B ${{ inputs.release && 'release=1' || '' }} interpreter=1
- name: Download shards release
uses: actions/checkout@v4
Expand Down
6 changes: 6 additions & 0 deletions spec/std/http/client/client_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ require "http/server"
require "http/log"
require "log/spec"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending HTTP::Client
{% skip_file %}
{% end %}

private def test_server(host, port, read_time = 0.seconds, content_type = "text/plain", write_response = true, &)
server = TCPServer.new(host, port)
begin
Expand Down
6 changes: 6 additions & 0 deletions spec/std/http/server/server_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ require "http/client"
require "../../../support/ssl"
require "../../../support/channel"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending HTTP::Server
{% skip_file %}
{% end %}

# TODO: replace with `HTTP::Client.get` once it supports connecting to Unix socket (#2735)
private def unix_request(path)
UNIXSocket.open(path) do |io|
Expand Down
6 changes: 6 additions & 0 deletions spec/std/http/web_socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ require "../../support/fibers"
require "../../support/ssl"
require "../socket/spec_helper.cr"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending HTTP::WebSocket
{% skip_file %}
{% end %}

private def assert_text_packet(packet, size, final = false)
assert_packet packet, HTTP::WebSocket::Protocol::Opcode::TEXT, size, final: final
end
Expand Down
33 changes: 18 additions & 15 deletions spec/std/io/io_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -816,23 +816,26 @@ describe IO do
io.gets_to_end.should eq("\r\nFoo\nBar")
end

it "gets ascii from socket (#9056)" do
server = TCPServer.new "localhost", 0
sock = TCPSocket.new "localhost", server.local_address.port
begin
sock.set_encoding("ascii")
spawn do
client = server.accept
message = client.gets
client << "#{message}\n"
# TODO: Windows networking in the interpreter requires #12495
{% unless flag?(:interpreted) || flag?(:win32) %}
it "gets ascii from socket (#9056)" do
server = TCPServer.new "localhost", 0
sock = TCPSocket.new "localhost", server.local_address.port
begin
sock.set_encoding("ascii")
spawn do
client = server.accept
message = client.gets
client << "#{message}\n"
end
sock << "K\n"
sock.gets.should eq("K")
ensure
server.close
sock.close
end
sock << "K\n"
sock.gets.should eq("K")
ensure
server.close
sock.close
end
end
{% end %}
end

describe "encode" do
Expand Down
6 changes: 6 additions & 0 deletions spec/std/oauth2/client_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ require "oauth2"
require "http/server"
require "../http/spec_helper"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending OAuth2::Client
{% skip_file %}
{% end %}

describe OAuth2::Client do
describe "authorization uri" do
it "gets with default endpoint" do
Expand Down
6 changes: 6 additions & 0 deletions spec/std/openssl/ssl/server_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ require "socket"
require "../../spec_helper"
require "../../../support/ssl"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending OpenSSL::SSL::Server
{% skip_file %}
{% end %}

describe OpenSSL::SSL::Server do
it "sync_close" do
TCPServer.open(0) do |tcp_server|
Expand Down
6 changes: 6 additions & 0 deletions spec/std/openssl/ssl/socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ require "../../spec_helper"
require "../../socket/spec_helper"
require "../../../support/ssl"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending OpenSSL::SSL::Socket
{% skip_file %}
{% end %}

describe OpenSSL::SSL::Socket do
describe OpenSSL::SSL::Socket::Server do
it "auto accept client by default" do
Expand Down
7 changes: 6 additions & 1 deletion spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ private def newline
end

# interpreted code doesn't receive SIGCHLD for `#wait` to work (#12241)
pending_interpreted describe: Process do
{% if flag?(:interpreted) && !flag?(:win32) %}
pending Process
{% skip_file %}
{% end %}

describe Process do
describe ".new" do
it "raises if command doesn't exist" do
expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do
Expand Down
6 changes: 6 additions & 0 deletions spec/std/socket/socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ require "./spec_helper"
require "../../support/tempfile"
require "../../support/win32"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending Socket
{% skip_file %}
{% end %}

describe Socket, tags: "network" do
describe ".unix" do
it "creates a unix socket" do
Expand Down
6 changes: 6 additions & 0 deletions spec/std/socket/tcp_socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
require "./spec_helper"
require "../../support/win32"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending TCPSocket
{% skip_file %}
{% end %}

describe TCPSocket, tags: "network" do
describe "#connect" do
each_ip_family do |family, address|
Expand Down
6 changes: 6 additions & 0 deletions spec/std/socket/unix_server_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ require "../../support/fibers"
require "../../support/channel"
require "../../support/tempfile"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending UNIXServer
{% skip_file %}
{% end %}

describe UNIXServer do
describe ".new" do
it "raises when path is too long" do
Expand Down
6 changes: 6 additions & 0 deletions spec/std/socket/unix_socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ require "spec"
require "socket"
require "../../support/tempfile"

# TODO: Windows networking in the interpreter requires #12495
{% if flag?(:interpreted) && flag?(:win32) %}
pending UNIXSocket
{% skip_file %}
{% end %}

describe UNIXSocket do
it "raises when path is too long" do
with_tempfile("unix_socket-too_long-#{("a" * 2048)}.sock") do |path|
Expand Down
1 change: 1 addition & 0 deletions spec/std/uuid_spec.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "spec"
require "uuid"
require "spec/helpers/string"
require "../support/wasm32"

describe "UUID" do
describe "#==" do
Expand Down
37 changes: 28 additions & 9 deletions src/compiler/crystal/loader/msvc.cr
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,25 @@ class Crystal::Loader
end

def load_file?(path : String | ::Path) : Bool
# API sets shouldn't be linked directly from linker flags, but just in case
if api_set?(path)
return load_dll?(path.to_s)
end

return false unless File.file?(path)

# On Windows, each `.lib` import library may reference any number of `.dll`
# files, whose base names may not match the library's. Thus it is necessary
# to extract this information from the library archive itself.
System::LibraryArchive.imported_dlls(path).each do |dll|
dll_full_path = @dll_search_paths.try &.each do |search_path|
full_path = File.join(search_path, dll)
break full_path if File.file?(full_path)
System::LibraryArchive.imported_dlls(path).all? do |dll|
# API set names do not refer to physical filenames despite ending with
# `.dll`, and therefore should not use a path search:
# https://learn.microsoft.com/en-us/cpp/windows/universal-crt-deployment?view=msvc-170#local-deployment
unless api_set?(dll)
dll_full_path = @dll_search_paths.try &.each do |search_path|
full_path = File.join(search_path, dll)
break full_path if File.file?(full_path)
end
end
dll = dll_full_path || dll

Expand All @@ -152,13 +162,16 @@ class Crystal::Loader
#
# Note that the compiler's directory and PATH are effectively searched
# twice when coming from the interpreter
handle = open_library(dll)
return false unless handle

@handles << handle
@loaded_libraries << (module_filename(handle) || dll)
load_dll?(dll)
end
end

private def load_dll?(dll)
handle = open_library(dll)
return false unless handle

@handles << handle
@loaded_libraries << (module_filename(handle) || dll)
true
end

Expand Down Expand Up @@ -190,6 +203,12 @@ class Crystal::Loader
@handles.clear
end

# Returns whether *dll* names an API set according to:
# https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets#api-set-contract-names
private def api_set?(dll)
dll.to_s.matches?(/^(?:api-|ext-)[a-zA-Z0-9-]*l\d+-\d+-\d+\.dll$/)
end

private def module_filename(handle)
Crystal::System.retry_wstr_buffer do |buffer, small_buf|
len = LibC.GetModuleFileNameW(handle, buffer, buffer.size)
Expand Down
13 changes: 13 additions & 0 deletions src/kernel.cr
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,16 @@ end
Crystal::System::Signal.setup_default_handlers
{% end %}
{% end %}

# This is a temporary workaround to ensure there is always something in the IOCP
# event loop being awaited, since both the interrupt loop and the fiber stack
# pool collector are disabled in interpreted code. Without this, asynchronous
# code that bypasses `Crystal::IOCP::OverlappedOperation` does not currently
# work, see https://github.com/crystal-lang/crystal/pull/14949#issuecomment-2328314463
{% if flag?(:interpreted) && flag?(:win32) %}
spawn(name: "Interpreter idle loop") do
while true
sleep 1.day
end
end
{% end %}

0 comments on commit cdd9ccf

Please sign in to comment.