diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 5c7be225ab00..9c623f627e43 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -45,8 +45,9 @@ jobs: target: - aarch64-darwin - arm-linux-gnueabihf - - i386-linux-musl - i386-linux-gnu + - i386-linux-musl + - wasm32-unknown-wasi - x86_64-dragonfly - x86_64-freebsd - x86_64-netbsd @@ -56,5 +57,9 @@ jobs: - name: Download Crystal source uses: actions/checkout@v2 + - name: Make compiler (special case only for wasm32, until next release). + if: ${{ matrix.target == 'wasm32-unknown-wasi' }} + run: bin/ci with_build_env make + - name: Run smoke test run: bin/ci with_build_env make smoke_test target=${{ matrix.target }} diff --git a/spec/compiler/ffi/ffi_spec.cr b/spec/compiler/ffi/ffi_spec.cr index 2ef0fe19d5da..4b70658e6bb8 100644 --- a/spec/compiler/ffi/ffi_spec.cr +++ b/spec/compiler/ffi/ffi_spec.cr @@ -1,4 +1,4 @@ -{% skip_file if !flag?(:unix) || flag?(:without_ffi) %} +{% skip_file if !flag?(:unix) || flag?(:without_ffi) || flag?(:wasm32) %} require "../spec_helper" require "compiler/crystal/ffi" diff --git a/spec/compiler/loader/unix_spec.cr b/spec/compiler/loader/unix_spec.cr index e6b4be3d7d8b..cc2e9894b075 100644 --- a/spec/compiler/loader/unix_spec.cr +++ b/spec/compiler/loader/unix_spec.cr @@ -1,4 +1,4 @@ -{% skip_file unless flag?(:unix) %} +{% skip_file if !flag?(:unix) || flag?(:wasm32) %} require "./spec_helper" require "../spec_helper" diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index c8e75b23103b..8fea7f6cf57a 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:wasm32) %} + require "spec" require "process" require "./spec_helper" diff --git a/spec/std/signal_spec.cr b/spec/std/signal_spec.cr index c98b4a706bea..e70278aedb3e 100644 --- a/spec/std/signal_spec.cr +++ b/spec/std/signal_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:wasm32) %} + require "spec" require "signal" diff --git a/spec/std/socket/addrinfo_spec.cr b/spec/std/socket/addrinfo_spec.cr index f649274e3346..615058472525 100644 --- a/spec/std/socket/addrinfo_spec.cr +++ b/spec/std/socket/addrinfo_spec.cr @@ -74,8 +74,8 @@ describe Socket::Addrinfo, tags: "network" do end describe "Error" do - {% unless flag?(:win32) %} - # This method is not available on windows because windows support was introduced after deprecation. + {% unless flag?(:win32) || flag?(:wasm32) %} + # This method is not available on windows/wasm because windows/wasm support was introduced after deprecation. it ".new (deprecated)" do error = Socket::Addrinfo::Error.new(LibC::EAI_NONAME, "No address found", "foobar.com") error.os_error.should eq Errno.new(LibC::EAI_NONAME) diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index b01d0b3092ea..454294bef2c0 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:wasm32) %} + require "./spec_helper" require "../../support/win32" diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 8a0e94fef57b..fc974f862445 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -1,3 +1,5 @@ +{% skip_file if flag?(:wasm32) %} + require "./spec_helper" require "../../support/win32" diff --git a/spec/std/string_spec.cr b/spec/std/string_spec.cr index d0a1c7087389..cad95bc3c69b 100644 --- a/spec/std/string_spec.cr +++ b/spec/std/string_spec.cr @@ -2719,12 +2719,14 @@ describe "String" do string.should be(clone) end - it "allocates buffer of correct size when UInt8 is given to new (#3332)" do - String.new(255_u8) do |buffer| - LibGC.size(buffer).should be >= 255 - {255, 0} + {% unless flag?(:wasm32) %} + it "allocates buffer of correct size when UInt8 is given to new (#3332)" do + String.new(255_u8) do |buffer| + LibGC.size(buffer).should be >= 255 + {255, 0} + end end - end + {% end %} it "raises on String.new if returned bytesize is greater than capacity" do expect_raises ArgumentError, "Bytesize out of capacity bounds" do diff --git a/src/compiler/crystal/codegen/link.cr b/src/compiler/crystal/codegen/link.cr index 4caf0060c789..cc764678b990 100644 --- a/src/compiler/crystal/codegen/link.cr +++ b/src/compiler/crystal/codegen/link.cr @@ -100,7 +100,11 @@ module Crystal class Program def object_extension - has_flag?("windows") ? ".obj" : ".o" + case + when has_flag?("windows") then ".obj" + when has_flag?("wasm32") then ".wasm" + else ".o" + end end def lib_flags diff --git a/src/compiler/crystal/codegen/target.cr b/src/compiler/crystal/codegen/target.cr index 4ab099e1a193..c89880deabda 100644 --- a/src/compiler/crystal/codegen/target.cr +++ b/src/compiler/crystal/codegen/target.cr @@ -96,12 +96,16 @@ class Crystal::Codegen::Target @environment.starts_with?("linux") end + def wasi? + @environment.starts_with?("wasi") + end + def bsd? freebsd? || netbsd? || openbsd? || dragonfly? end def unix? - macos? || bsd? || linux? + macos? || bsd? || linux? || wasi? end def gnu? @@ -144,6 +148,8 @@ class Crystal::Codegen::Target if cpu.empty? && !features.includes?("fp") && armhf? features += "+vfp2" end + when "wasm32" + LLVM.init_webassembly else raise Target::Error.new("Unsupported architecture for target triple: #{self}") end diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index a1075d1069e0..3aa966f5d694 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -247,7 +247,7 @@ class Crystal::Command begin elapsed = Time.measure do Process.run(output_filename, args: run_args, input: Process::Redirect::Inherit, output: Process::Redirect::Inherit, error: Process::Redirect::Inherit) do |process| - {% unless flag?(:win32) %} + {% unless flag?(:win32) || flag?(:wasm32) %} # Ignore the signal so we don't exit the running process # (the running process can still handle this signal) ::Signal::INT.ignore # do diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index 98e163131fea..94a75518f053 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -394,6 +394,9 @@ module Crystal end {cmd, nil} + elsif program.has_flag? "wasm32" + link_flags = @link_flags || "" + { %(wasm-ld "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} -lc #{program.lib_flags}), object_names } else link_flags = @link_flags || "" link_flags += " -rdynamic" diff --git a/src/crystal/system.cr b/src/crystal/system.cr index 88827a3f6552..953137edc730 100644 --- a/src/crystal/system.cr +++ b/src/crystal/system.cr @@ -8,7 +8,10 @@ module Crystal::System # def self.cpu_count end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./system/wasi/hostname" + require "./system/wasi/cpucount" +{% elsif flag?(:unix) %} require "./system/unix/hostname" {% if flag?(:bsd) %} diff --git a/src/crystal/system/dir.cr b/src/crystal/system/dir.cr index 33d983c8e411..eef9f330fbab 100644 --- a/src/crystal/system/dir.cr +++ b/src/crystal/system/dir.cr @@ -51,7 +51,9 @@ module Crystal::System::Dir # def self.delete(path : String) : Nil end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/dir" +{% elsif flag?(:unix) %} require "./unix/dir" {% elsif flag?(:win32) %} require "./win32/dir" diff --git a/src/crystal/system/event_loop.cr b/src/crystal/system/event_loop.cr index 0ca574ce82a1..d7dc351c6ae2 100644 --- a/src/crystal/system/event_loop.cr +++ b/src/crystal/system/event_loop.cr @@ -28,7 +28,9 @@ struct Crystal::Event # def add(time_span : Time::Span?) : Nil end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/event_loop" +{% elsif flag?(:unix) %} require "./unix/event_loop_libevent" {% elsif flag?(:win32) %} require "./win32/event_loop_iocp" diff --git a/src/crystal/system/fiber.cr b/src/crystal/system/fiber.cr index 23794756bc4b..230cf4cc010b 100644 --- a/src/crystal/system/fiber.cr +++ b/src/crystal/system/fiber.cr @@ -9,7 +9,9 @@ module Crystal::System::Fiber # def self.main_fiber_stack(stack_bottom : Void*) : Void* end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/fiber" +{% elsif flag?(:unix) %} require "./unix/fiber" {% elsif flag?(:win32) %} require "./win32/fiber" diff --git a/src/crystal/system/file.cr b/src/crystal/system/file.cr index f4d3ced9498c..1c5b3a0edb00 100644 --- a/src/crystal/system/file.cr +++ b/src/crystal/system/file.cr @@ -49,7 +49,9 @@ module Crystal::System::File # def file_descriptor_close end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/file" +{% elsif flag?(:unix) %} require "./unix/file" {% elsif flag?(:win32) %} require "./win32/file" diff --git a/src/crystal/system/file_descriptor.cr b/src/crystal/system/file_descriptor.cr index c484503d711c..6e5621e57786 100644 --- a/src/crystal/system/file_descriptor.cr +++ b/src/crystal/system/file_descriptor.cr @@ -1,4 +1,6 @@ -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/file_descriptor" +{% elsif flag?(:unix) %} require "./unix/file_descriptor" {% elsif flag?(:win32) %} require "./win32/file_descriptor" diff --git a/src/crystal/system/group.cr b/src/crystal/system/group.cr index 56ffdc1f39a8..dce631e8c1ab 100644 --- a/src/crystal/system/group.cr +++ b/src/crystal/system/group.cr @@ -1,4 +1,6 @@ -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/group" +{% elsif flag?(:unix) %} require "./unix/group" {% else %} {% raise "No Crystal::System::Group implementation available" %} diff --git a/src/crystal/system/path.cr b/src/crystal/system/path.cr index 17091ec3fc86..b421e1a46357 100644 --- a/src/crystal/system/path.cr +++ b/src/crystal/system/path.cr @@ -3,7 +3,9 @@ module Crystal::System::Path # def self.home : String end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/path" +{% elsif flag?(:unix) %} require "./unix/path" {% elsif flag?(:win32) %} require "./win32/path" diff --git a/src/crystal/system/process.cr b/src/crystal/system/process.cr index fca85f7a6c7e..b83d373382b7 100644 --- a/src/crystal/system/process.cr +++ b/src/crystal/system/process.cr @@ -70,7 +70,9 @@ module Crystal::System ORIGINAL_STDERR = IO::FileDescriptor.new(2, blocking: true) end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/process" +{% elsif flag?(:unix) %} require "./unix/process" {% elsif flag?(:win32) %} require "./win32/process" diff --git a/src/crystal/system/random.cr b/src/crystal/system/random.cr index 4581a3011f33..becb879cedbb 100644 --- a/src/crystal/system/random.cr +++ b/src/crystal/system/random.cr @@ -10,7 +10,9 @@ module Crystal::System::Random # def self.next_u end -{% if flag?(:linux) %} +{% if flag?(:wasi) %} + require "./wasi/random" +{% elsif flag?(:linux) %} require "./unix/getrandom" {% elsif flag?(:openbsd) || flag?(:netbsd) %} require "./unix/arc4random" diff --git a/src/crystal/system/socket.cr b/src/crystal/system/socket.cr index fa7df866a22f..54c49f62adaa 100644 --- a/src/crystal/system/socket.cr +++ b/src/crystal/system/socket.cr @@ -76,7 +76,9 @@ module Crystal::System::Socket # private def system_tcp_keepalive_count=(val : Int) end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/socket" +{% elsif flag?(:unix) %} require "./unix/socket" {% elsif flag?(:win32) %} require "./win32/socket" diff --git a/src/crystal/system/thread.cr b/src/crystal/system/thread.cr index ffaf51555782..71f6286d9d36 100644 --- a/src/crystal/system/thread.cr +++ b/src/crystal/system/thread.cr @@ -28,7 +28,9 @@ end require "./thread_linked_list" -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/thread" +{% elsif flag?(:unix) %} require "./unix/pthread" require "./unix/pthread_condition_variable" {% elsif flag?(:win32) %} diff --git a/src/crystal/system/thread_mutex.cr b/src/crystal/system/thread_mutex.cr index ced2563cefbc..1a5f3006d6eb 100644 --- a/src/crystal/system/thread_mutex.cr +++ b/src/crystal/system/thread_mutex.cr @@ -17,7 +17,9 @@ class Thread end end -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/thread_mutex" +{% elsif flag?(:unix) %} require "./unix/pthread_mutex" {% elsif flag?(:win32) %} require "./win32/thread_mutex" diff --git a/src/crystal/system/user.cr b/src/crystal/system/user.cr index a065bb4e712d..ecee92c8dcb5 100644 --- a/src/crystal/system/user.cr +++ b/src/crystal/system/user.cr @@ -1,4 +1,6 @@ -{% if flag?(:unix) %} +{% if flag?(:wasi) %} + require "./wasi/user" +{% elsif flag?(:unix) %} require "./unix/user" {% else %} {% raise "No Crystal::System::User implementation available" %} diff --git a/src/crystal/system/wasi/cpucount.cr b/src/crystal/system/wasi/cpucount.cr new file mode 100644 index 000000000000..f6f068b09ae2 --- /dev/null +++ b/src/crystal/system/wasi/cpucount.cr @@ -0,0 +1,6 @@ +module Crystal::System + def self.cpu_count + # TODO: There isn't a good way to get the number of CPUs on WebAssembly + 1 + end +end diff --git a/src/crystal/system/wasi/dir.cr b/src/crystal/system/wasi/dir.cr new file mode 100644 index 000000000000..7f6ee0056cdb --- /dev/null +++ b/src/crystal/system/wasi/dir.cr @@ -0,0 +1,106 @@ +require "./wasi" + +module Crystal::System::Dir + private class DirHandle + property fd : LibWasi::Fd + property buf = Bytes.new(4096) + property pos = 4096u32 + property end_pos = 4096u32 + property cookie = 0u64 + + def initialize(@fd) + end + + def fill_buffer(path) + err = LibWasi.fd_readdir(@fd, @buf, @buf.size, @cookie, pointerof(@end_pos)) + raise ::File::Error.from_os_error("Error reading directory entries", err, file: path) unless err.success? + @pos = 0 + end + end + + def self.open(path : String) : DirHandle + parent_fd, relative_path = Wasi.find_path_preopen(path) + + err = LibWasi.path_open(parent_fd, LibWasi::LookupFlags::SymlinkFollow, relative_path, LibWasi::OpenFlags::Directory, LibWasi::Rights::FdReaddir, LibWasi::Rights::None, LibWasi::FdFlags::None, out fd) + raise ::File::Error.from_os_error("Error opening directory", err, file: path) unless err.success? + + DirHandle.new(fd) + end + + def self.next_entry(dir, path) : Entry? + if dir.end_pos < dir.buf.size && dir.pos >= dir.end_pos + return nil + end + + if dir.pos + sizeof(LibWasi::DirEnt) > dir.buf.size + dir.fill_buffer(path) + end + + dirent = Pointer(LibWasi::DirEnt).new(dir.buf.to_unsafe.address + dir.pos).value + + if dir.pos + sizeof(LibWasi::DirEnt) + dirent.d_namlen > dir.buf.size + if dir.pos == 0 + dir.buf = Bytes.new(dir.buf.size * 2) + end + dir.fill_buffer(path) + return next_entry(dir, path) + end + + name = String.new(dir.buf[dir.pos + sizeof(LibWasi::DirEnt), dirent.d_namlen]) + dir.pos += sizeof(LibWasi::DirEnt) + dirent.d_namlen + dir.cookie = dirent.d_next + + is_dir = case dirent.d_type + when .directory? then true + when .unknown?, .symbolic_link? then nil + else false + end + + Entry.new(name, is_dir) + end + + def self.rewind(dir) : Nil + dir.cookie = 0 + dir.end_pos = dir.pos = dir.buf.size.to_u32 + end + + def self.close(dir, path) : Nil + err = LibWasi.fd_close(dir.fd) + raise ::File::Error.from_os_error("Error closing directory", err, file: path) unless err.success? + end + + def self.current : String + unless dir = LibC.getcwd(nil, 0) + raise ::File::Error.from_errno("Error getting current directory", file: "./") + end + + dir_str = String.new(dir) + LibC.free(dir.as(Void*)) + dir_str + end + + def self.current=(path : String) + if LibC.chdir(path.check_no_null_byte) != 0 + raise ::File::Error.from_errno("Error while changing directory", file: path) + end + + path + end + + def self.tempdir + tmpdir = ENV["TMPDIR"]? || "/tmp" + tmpdir.rchop(::File::SEPARATOR) + end + + def self.create(path : String, mode : Int32) : Nil + if LibC.mkdir(path.check_no_null_byte, mode) == -1 + raise ::File::Error.from_errno("Unable to create directory", file: path) + end + end + + def self.delete(path : String) : Nil + if LibC.rmdir(path.check_no_null_byte) == -1 + raise ::File::Error.from_errno("Unable to remove directory", file: path) + end + end +end diff --git a/src/crystal/system/wasi/event_loop.cr b/src/crystal/system/wasi/event_loop.cr new file mode 100644 index 000000000000..0d8eaf6f780a --- /dev/null +++ b/src/crystal/system/wasi/event_loop.cr @@ -0,0 +1,41 @@ +# :nodoc: +module Crystal::EventLoop + # Runs the event loop. + def self.run_once + raise NotImplementedError.new("Crystal::EventLoop.run_once") + end + + # Create a new resume event for a fiber. + def self.create_resume_event(fiber : Fiber) : Crystal::Event + raise NotImplementedError.new("Crystal::EventLoop.create_resume_event") + end + + # Creates a timeout_event. + def self.create_timeout_event(fiber) : Crystal::Event + raise NotImplementedError.new("Crystal::EventLoop.create_timeout_event") + end + + # Creates a write event for a file descriptor. + def self.create_fd_write_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event + raise NotImplementedError.new("Crystal::EventLoop.create_fd_write_event") + end + + # Creates a read event for a file descriptor. + def self.create_fd_read_event(io : IO::Evented, edge_triggered : Bool = false) : Crystal::Event + raise NotImplementedError.new("Crystal::EventLoop.create_fd_read_event") + end +end + +struct Crystal::Event + def add(timeout : LibC::Timeval? = nil) + end + + def add(timeout : Time::Span) + end + + def free + end + + def delete + end +end diff --git a/src/crystal/system/wasi/fiber.cr b/src/crystal/system/wasi/fiber.cr new file mode 100644 index 000000000000..cabb06a9cd6e --- /dev/null +++ b/src/crystal/system/wasi/fiber.cr @@ -0,0 +1,9 @@ +module Crystal::System::Fiber + def self.allocate_stack(stack_size) : Void* + LibC.malloc(stack_size) + end + + def self.free_stack(stack : Void*, stack_size) : Nil + LibC.free(stack) + end +end diff --git a/src/crystal/system/wasi/file.cr b/src/crystal/system/wasi/file.cr new file mode 100644 index 000000000000..c3c77dae2714 --- /dev/null +++ b/src/crystal/system/wasi/file.cr @@ -0,0 +1,40 @@ +require "../unix/file" + +# :nodoc: +module Crystal::System::File + def self.chmod(path, mode) + raise NotImplementedError.new "Crystal::System::File.chmod" + end + + def self.chown(path, uid : Int, gid : Int, follow_symlinks) + raise NotImplementedError.new "Crystal::System::File.chown" + end + + def self.real_path(path) + raise NotImplementedError.new "Crystal::System::File.real_path" + end + + def self.utime(atime : ::Time, mtime : ::Time, filename : String) : Nil + raise NotImplementedError.new "Crystal::System::File.utime" + end + + private def system_flock_shared(blocking) + raise NotImplementedError.new "Crystal::System::File#system_flock_shared" + end + + private def system_flock_exclusive(blocking) + raise NotImplementedError.new "Crystal::System::File#system_flock_exclusive" + end + + private def system_flock_unlock + raise NotImplementedError.new "Crystal::System::File#system_flock_unlock" + end + + private def flock(op : LibC::FlockOp, blocking : Bool = true) + raise NotImplementedError.new "Crystal::System::File#flock" + end + + def self.mktemp(prefix, suffix, dir) : {LibC::Int, String} + raise NotImplementedError.new "Crystal::System::File.mktemp" + end +end diff --git a/src/crystal/system/wasi/file_descriptor.cr b/src/crystal/system/wasi/file_descriptor.cr new file mode 100644 index 000000000000..7a375e89a136 --- /dev/null +++ b/src/crystal/system/wasi/file_descriptor.cr @@ -0,0 +1,17 @@ +require "../unix/file_descriptor" + +# :nodoc: +module Crystal::System::FileDescriptor + def self.from_stdio(fd) + # TODO: WASI doesn't offer a way to detect if a 'fd' is a TTY. + IO::FileDescriptor.new(fd).tap(&.flush_on_newline=(true)) + end + + def self.pipe(read_blocking, write_blocking) + raise NotImplementedError.new "Crystal::System::FileDescriptor.pipe" + end + + private def system_reopen(other : IO::FileDescriptor) + raise NotImplementedError.new "Crystal::System::FileDescriptor.system_reopen" + end +end diff --git a/src/crystal/system/wasi/group.cr b/src/crystal/system/wasi/group.cr new file mode 100644 index 000000000000..0aa09bd40aa8 --- /dev/null +++ b/src/crystal/system/wasi/group.cr @@ -0,0 +1,9 @@ +module Crystal::System::Group + private def from_name?(groupname : String) + raise NotImplementedError.new("Crystal::System::Group#from_name?") + end + + private def from_id?(groupid : String) + raise NotImplementedError.new("Crystal::System::Group#from_id?") + end +end diff --git a/src/crystal/system/wasi/hostname.cr b/src/crystal/system/wasi/hostname.cr new file mode 100644 index 000000000000..e59611d6a0a4 --- /dev/null +++ b/src/crystal/system/wasi/hostname.cr @@ -0,0 +1,5 @@ +module Crystal::System + def self.hostname + raise NotImplementedError.new("Crystal::System.hostname") + end +end diff --git a/src/crystal/system/wasi/lib_wasi.cr b/src/crystal/system/wasi/lib_wasi.cr new file mode 100644 index 000000000000..a958b6e46206 --- /dev/null +++ b/src/crystal/system/wasi/lib_wasi.cr @@ -0,0 +1,102 @@ +lib LibWasi + alias Fd = Int32 + alias Size = UInt32 + alias FileSize = UInt64 + + struct PrestatDir + pr_name_len : Size + end + + union PrestatUnion + dir : PrestatDir + end + + enum PrestatTag : UInt8 + Dir + end + + struct Prestat + tag : PrestatTag + value : PrestatUnion + end + + @[Flags] + enum LookupFlags : UInt32 + SymlinkFollow + end + + @[Flags] + enum OpenFlags : UInt16 + Creat + Directory + Excl + Trunc + end + + @[Flags] + enum FdFlags : UInt16 + Append # Append mode: Data written to the file is always appended to the file's end. + Dsync # Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. + NonBlock # Non-blocking mode. + RSync # Synchronized read I/O operations. + Sync # Write according to synchronized I/O file integrity completion. In addition to synchronizing the data stored in the file, the implementation may also synchronously update the file's metadata. + end + + @[Flags] + enum Rights : UInt64 + FdDatasync # The right to invoke fd_datasync. If path_open is set, includes the right to invoke path_open with fdflags::dsync. + FdRead # The right to invoke fd_read and sock_recv. If rights::fd_seek is set, includes the right to invoke fd_pread. + FdSeek # The right to invoke fd_seek. This flag implies rights::fd_tell. + FdFdstatSetFlags # The right to invoke fd_fdstat_set_flags. + FdSync # The right to invoke fd_sync. If path_open is set, includes the right to invoke path_open with fdflags::rsync and fdflags::dsync. + FdTell # The right to invoke fd_seek in such a way that the file offset remains unaltered (i.e., whence::cur with offset zero), or to invoke fd_tell. + FdWrite # The right to invoke fd_write and sock_send. If rights::fd_seek is set, includes the right to invoke fd_pwrite. + FdAdvise # The right to invoke fd_advise. + FdAllocate # The right to invoke fd_allocate. + PathCreateDirectory # The right to invoke path_create_directory. + PathCreateFile # If path_open is set, the right to invoke path_open with oflags::creat. + PathLinkSource # The right to invoke path_link with the file descriptor as the source directory. + PathLinkTarget # The right to invoke path_link with the file descriptor as the target directory. + PathOpen # The right to invoke path_open. + FdReaddir # The right to invoke fd_readdir. + PathReadlink # The right to invoke path_readlink. + PathRenameSource # The right to invoke path_rename with the file descriptor as the source directory. + PathRenameTarget # The right to invoke path_rename with the file descriptor as the target directory. + PathFilestatGet # The right to invoke path_filestat_get. + PathFilestatSetSize # The right to change a file's size (there is no path_filestat_set_size). If path_open is set, includes the right to invoke path_open with oflags::trunc. + PathFilestatSetTimes # The right to invoke path_filestat_set_times. + FdFilestatGet # The right to invoke fd_filestat_get. + FdFilestatSetSize # The right to invoke fd_filestat_set_size. + FdFilestatSetTimes # The right to invoke fd_filestat_set_times. + PathSymlink # The right to invoke path_symlink. + PathRemoveDirectory # The right to invoke path_remove_directory. + PathUnlinkFile # The right to invoke path_unlink_file. + PollFdReadwrite # If rights::fd_read is set, includes the right to invoke poll_oneoff to subscribe to eventtype::fd_read. If rights::fd_write is set, includes the right to invoke poll_oneoff to subscribe to eventtype::fd_write. + SockShutdown # The right to invoke sock_shutdown. + end + + enum FileType : UInt8 + Unknown # The type of the file descriptor or file is unknown or is different from any of the other types specified. + BlockDevice # The file descriptor or file refers to a block device inode. + CharacterDevice # The file descriptor or file refers to a character device inode. + Directory # The file descriptor or file refers to a directory inode. + RegularFile # The file descriptor or file refers to a regular file inode. + SocketDgram # The file descriptor or file refers to a datagram socket. + SocketStream # The file descriptor or file refers to a byte-stream socket. + SymbolicLink # The file refers to a symbolic link inode. + end + + struct DirEnt + d_next : UInt64 + d_ino : UInt64 + d_namlen : UInt32 + d_type : FileType + end + + fun fd_prestat_get = __wasi_fd_prestat_get(fd : Fd, stat : Prestat*) : WasiError + fun fd_prestat_dir_name = __wasi_fd_prestat_dir_name(fd : Fd, path : UInt8*, len : Size) : WasiError + fun path_open = __wasi_path_open(fd : Fd, dirflags : LookupFlags, path : UInt8*, oflags : OpenFlags, fs_rights_base : Rights, fs_rights_inheriting : Rights, fdflags : FdFlags, ret : Fd*) : WasiError + fun fd_readdir = __wasi_fd_readdir(fd : Fd, buf : UInt8*, len : Size, cookie : UInt64, ret : Size*) : WasiError + fun fd_close = __wasi_fd_close(fd : Fd) : WasiError + fun random_get = __wasi_random_get(buf : UInt8*, len : Size) : WasiError +end diff --git a/src/crystal/system/wasi/path.cr b/src/crystal/system/wasi/path.cr new file mode 100644 index 000000000000..5a570aa71dc8 --- /dev/null +++ b/src/crystal/system/wasi/path.cr @@ -0,0 +1,5 @@ +module Crystal::System::Path + def self.home : String + ENV["HOME"] + end +end diff --git a/src/crystal/system/wasi/process.cr b/src/crystal/system/wasi/process.cr new file mode 100644 index 000000000000..a9d5d0e9fa69 --- /dev/null +++ b/src/crystal/system/wasi/process.cr @@ -0,0 +1,78 @@ +require "c/stdlib" +require "c/unistd" + +struct Crystal::System::Process + getter pid : LibC::PidT + + def initialize(@pid : LibC::PidT) + end + + def release + raise NotImplementedError.new("Process#release") + end + + def wait + raise NotImplementedError.new("Process#wait") + end + + def exists? + raise NotImplementedError.new("Process#exists?") + end + + def terminate + raise NotImplementedError.new("Process#terminate") + end + + def self.exit(status) + LibC.exit(status) + end + + def self.pid + # TODO: WebAssembly doesn't have the concept of processes. + 1 + end + + def self.pgid + raise NotImplementedError.new("Process.pgid") + end + + def self.pgid(pid) + raise NotImplementedError.new("Process.pgid") + end + + def self.ppid + raise NotImplementedError.new("Process.ppid") + end + + def self.signal(pid, signal) + raise NotImplementedError.new("Process.signal") + end + + def self.exists?(pid) + raise NotImplementedError.new("Process.exists?") + end + + def self.times + raise NotImplementedError.new("Process.times") + end + + def self.fork(*, will_exec = false) + raise NotImplementedError.new("Process.fork") + end + + def self.spawn(command_args, env, clear_env, input, output, error, chdir) + raise NotImplementedError.new("Process.spawn") + end + + def self.prepare_args(command : String, args : Enumerable(String)?, shell : Bool) : Array(String) + raise NotImplementedError.new("Process.prepare_args") + end + + def self.replace(command_args, env, clear_env, input, output, error, chdir) + raise NotImplementedError.new("Process.replace") + end + + def self.chroot(path) + raise NotImplementedError.new("Process.chroot") + end +end diff --git a/src/crystal/system/wasi/random.cr b/src/crystal/system/wasi/random.cr new file mode 100644 index 000000000000..72649fac3e7c --- /dev/null +++ b/src/crystal/system/wasi/random.cr @@ -0,0 +1,14 @@ +require "./lib_wasi" + +module Crystal::System::Random + def self.random_bytes(buf : Bytes) : Nil + err = LibWasi.random_get(buf, buf.size) + raise RuntimeError.from_os_error("random_get", err) unless err.success? + end + + def self.next_u : UInt8 + buf = uninitialized UInt8[1] + random_bytes(buf.to_slice) + buf.unsafe_as(UInt8) + end +end diff --git a/src/crystal/system/wasi/socket.cr b/src/crystal/system/wasi/socket.cr new file mode 100644 index 000000000000..974613b847c4 --- /dev/null +++ b/src/crystal/system/wasi/socket.cr @@ -0,0 +1,185 @@ +require "c/netdb" +require "c/netinet/tcp" +require "c/sys/socket" +require "io/evented" + +module Crystal::System::Socket + include IO::Evented + + alias Handle = Int32 + + private def create_handle(family, type, protocol, blocking) : Handle + raise NotImplementedError.new "Crystal::System::Socket#create_handle" + end + + private def initialize_handle(fd) + end + + private def system_connect(addr, timeout = nil, &) + raise NotImplementedError.new "Crystal::System::Socket#system_connect" + end + + # Tries to bind the socket to a local address. + # Yields an `Socket::BindError` if the binding failed. + private def system_bind(addr, addrstr, &) + raise NotImplementedError.new "Crystal::System::Socket#system_bind" + end + + private def system_listen(backlog, &) + raise NotImplementedError.new "Crystal::System::Socket#system_listen" + end + + private def system_accept + (raise NotImplementedError.new "Crystal::System::Socket#system_accept").as(Int32) + end + + private def system_send(bytes : Bytes) : Int32 + evented_send(bytes, "Error sending datagram") do |slice| + LibC.send(fd, slice.to_unsafe.as(Void*), slice.size, 0) + end + end + + private def system_send_to(bytes : Bytes, addr : ::Socket::Address) + raise NotImplementedError.new "Crystal::System::Socket#system_send_to" + end + + private def system_receive(bytes) + raise NotImplementedError.new "Crystal::System::Socket#system_receive" + end + + private def system_close_read + if LibC.shutdown(fd, LibC::SHUT_RD) != 0 + raise ::Socket::Error.from_errno("shutdown read") + end + end + + private def system_close_write + if LibC.shutdown(fd, LibC::SHUT_WR) != 0 + raise ::Socket::Error.from_errno("shutdown write") + end + end + + private def system_reuse_port? + raise NotImplementedError.new "Crystal::System::Socket#system_reuse_port?" + end + + private def system_reuse_port=(val : Bool) + raise NotImplementedError.new "Crystal::System::Socket#system_reuse_port=" + end + + private def system_linger + raise NotImplementedError.new "Crystal::System::Socket#system_linger" + end + + private def system_linger=(val) + raise NotImplementedError.new "Crystal::System::Socket#system_linge=" + end + + private def system_getsockopt(fd, optname, optval, level = LibC::SOL_SOCKET, &) + raise NotImplementedError.new "Crystal::System::Socket#system_getsockopt" + end + + private def system_setsockopt(fd, optname, optval, level = LibC::SOL_SOCKET) + raise NotImplementedError.new "Crystal::System::Socket#system_setsockopt" + end + + private def system_blocking? + fcntl(LibC::F_GETFL) & LibC::O_NONBLOCK == 0 + end + + private def system_blocking=(value) + flags = fcntl(LibC::F_GETFL) + if value + flags &= ~LibC::O_NONBLOCK + else + flags |= LibC::O_NONBLOCK + end + fcntl(LibC::F_SETFL, flags) + end + + private def system_close_on_exec? + flags = fcntl(LibC::F_GETFD) + (flags & LibC::FD_CLOEXEC) == LibC::FD_CLOEXEC + end + + private def system_close_on_exec=(arg : Bool) + fcntl(LibC::F_SETFD, arg ? LibC::FD_CLOEXEC : 0) + arg + end + + def self.fcntl(fd, cmd, arg = 0) + r = LibC.fcntl fd, cmd, arg + raise ::Socket::Error.from_errno("fcntl() failed") if r == -1 + r + end + + private def system_tty? + LibC.isatty(fd) == 1 + end + + private def unbuffered_read(slice : Bytes) + evented_read(slice, "Error reading socket") do + LibC.recv(fd, slice, slice.size, 0).to_i32 + end + end + + private def unbuffered_write(slice : Bytes) + evented_write(slice, "Error writing to socket") do |slice| + LibC.send(fd, slice, slice.size, 0) + end + end + + private def system_close + # Perform libevent cleanup before LibC.close. + # Using a file descriptor after it has been closed is never defined and can + # always lead to undefined results. This is not specific to libevent. + evented_close + + # Clear the @volatile_fd before actually closing it in order to + # reduce the chance of reading an outdated fd value + fd = @volatile_fd.swap(-1) + + ret = LibC.close(fd) + + if ret != 0 + case Errno.value + when Errno::EINTR, Errno::EINPROGRESS + # ignore + else + raise ::Socket::Error.from_errno("Error closing socket") + end + end + end + + private def system_local_address + raise NotImplementedError.new "Crystal::System::Socket#system_local_address" + end + + private def system_remote_address + raise NotImplementedError.new "Crystal::System::Socket#system_remote_address" + end + + private def system_tcp_keepalive_idle + raise NotImplementedError.new("Crystal::System::Socket#system_tcp_keepalive_idle") + end + + private def system_tcp_keepalive_idle=(val : Int) + raise NotImplementedError.new("Crystal::System::Socket#system_tcp_keepalive_idle=") + end + + private def system_tcp_keepalive_interval + raise NotImplementedError.new("Crystal::System::Socket#system_tcp_keepalive_interval") + end + + private def system_tcp_keepalive_interval=(val : Int) + raise NotImplementedError.new("Crystal::System::Socket#system_tcp_keepalive_interval=") + end + + private def system_tcp_keepalive_count + raise NotImplementedError.new("Crystal::System::Socket#system_tcp_keepalive_count") + end + + private def system_tcp_keepalive_count=(val : Int) + raise NotImplementedError.new("Crystal::System::Socket#system_tcp_keepalive_count=") + end +end diff --git a/src/crystal/system/wasi/thread.cr b/src/crystal/system/wasi/thread.cr new file mode 100644 index 000000000000..b10439852f55 --- /dev/null +++ b/src/crystal/system/wasi/thread.cr @@ -0,0 +1,73 @@ +class Thread + @main_fiber : Fiber? + + def initialize + @main_fiber = Fiber.new(stack_address, self) + + # TODO: Create thread + end + + def initialize(&func : ->) + initialize + end + + def join : Nil + raise NotImplementedError.new("Thread#join") + end + + def self.yield : Nil + raise NotImplementedError.new("Thread.yield") + end + + @@current = Thread.new + + # Associates the Thread object to the running system thread. + protected def self.current=(@@current : Thread) : Thread + end + + # Returns the Thread object associated to the running system thread. + def self.current : Thread + @@current + end + + # Create the thread object for the current thread (aka the main thread of the + # process). + # + # TODO: consider moving to `kernel.cr` or `crystal/main.cr` + self.current = new + + # Returns the Fiber representing the thread's main stack. + def main_fiber + @main_fiber.not_nil! + end + + # :nodoc: + def scheduler + @scheduler ||= Crystal::Scheduler.new(main_fiber) + end + + protected def start + raise NotImplementedError.new("Thread#start") + end + + private def stack_address : Void* + # TODO: Implement + Pointer(Void).null + end + + # :nodoc: + # TODO: Implement + class ConditionVariable + def signal : Nil + end + + def broadcast : Nil + end + + def wait(mutex : Thread::Mutex) : Nil + end + + def wait(mutex : Thread::Mutex, time : Time::Span, &) + end + end +end diff --git a/src/crystal/system/wasi/thread_mutex.cr b/src/crystal/system/wasi/thread_mutex.cr new file mode 100644 index 000000000000..0d90bebb67f8 --- /dev/null +++ b/src/crystal/system/wasi/thread_mutex.cr @@ -0,0 +1,17 @@ +# TODO: Implement +class Thread + class Mutex + def lock + end + + def try_lock + end + + def unlock + end + + def synchronize + yield + end + end +end diff --git a/src/crystal/system/wasi/user.cr b/src/crystal/system/wasi/user.cr new file mode 100644 index 000000000000..06415897000e --- /dev/null +++ b/src/crystal/system/wasi/user.cr @@ -0,0 +1,9 @@ +module Crystal::System::User + private def from_username?(username : String) + raise NotImplementedError.new("Crystal::System::User#from_username?") + end + + private def from_id?(id : String) + raise NotImplementedError.new("Crystal::System::User#from_id?") + end +end diff --git a/src/crystal/system/wasi/wasi.cr b/src/crystal/system/wasi/wasi.cr new file mode 100644 index 000000000000..3db4f600df1c --- /dev/null +++ b/src/crystal/system/wasi/wasi.cr @@ -0,0 +1,52 @@ +require "./lib_wasi" + +module Crystal::System::Wasi + PREOPENS = begin + preopens = [] of {String, String, LibWasi::Fd} + + # Skip stdin, stdout, and stderr, and count up until we reach an invalid file descriptor. + (3..).each do |fd| + stat = uninitialized LibWasi::Prestat + err = LibWasi.fd_prestat_get(fd, pointerof(stat)) + + break unless err.success? + + next unless stat.tag.dir? + + len = stat.value.dir.pr_name_len + + name = String.new(len + 1) do |buffer| + err = LibWasi.fd_prestat_dir_name(fd, buffer, len) + raise RuntimeError.from_os_error("fd_prestat_dir_name", err) unless err.success? + buffer[len] = 0 + len = LibC.strlen(buffer) + {len, 0} + end + + path = ::Path[name].expand.to_s + preopens << {path, path.ends_with?("/") ? path : path + "/", fd} + end + + # Preopens added later take priority over preopens added earlier. + preopens.reverse! + # Preopens of longer prefix take priority over shorter prefixes. + preopens.sort_by! { |entry| -entry[0].size } + + preopens + end + + def self.find_path_preopen(path) + path = ::Path[path].expand.to_s + PREOPENS.each do |preopen| + case path + when preopen[0] + return {preopen[2], "."} + when .starts_with? preopen[1] + return {preopen[2], path[preopen[1].size..-1]} + end + end + + # If we can't find a preopen for it, indicate that we lack capabilities. + raise RuntimeError.from_os_error(nil, WasiError::NOTCAPABLE) + end +end diff --git a/src/errno.cr b/src/errno.cr index 9a3afe5b5899..cc05673b12f9 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -4,6 +4,8 @@ require "c/string" lib LibC {% if flag?(:linux) || flag?(:dragonfly) %} fun __errno_location : Int* + {% elsif flag?(:wasi) %} + $errno : Int {% elsif flag?(:darwin) || flag?(:freebsd) %} fun __error : Int* {% elsif flag?(:netbsd) || flag?(:openbsd) %} @@ -43,6 +45,8 @@ enum Errno def self.value : self {% if flag?(:linux) || flag?(:dragonfly) %} Errno.new LibC.__errno_location.value + {% elsif flag?(:wasi) %} + Errno.new LibC.errno {% elsif flag?(:darwin) || flag?(:bsd) %} Errno.new LibC.__error.value {% elsif flag?(:win32) %} diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr index b59ea603235f..b1e57010d0d3 100644 --- a/src/exception/call_stack.cr +++ b/src/exception/call_stack.cr @@ -2,6 +2,8 @@ require "./call_stack/stackwalk" {% elsif flag?(:interpreted) %} require "./call_stack/interpreter" +{% elsif flag?(:wasm32) %} + require "./call_stack/null" {% else %} require "./call_stack/libunwind" {% end %} @@ -38,62 +40,66 @@ struct Exception::CallStack end private def decode_backtrace - show_full_info = ENV["CRYSTAL_CALLSTACK_FULL_INFO"]? == "1" + {% if flag?(:wasm32) %} + [] of String + {% else %} + show_full_info = ENV["CRYSTAL_CALLSTACK_FULL_INFO"]? == "1" - @callstack.compact_map do |ip| - pc = CallStack.decode_address(ip) + @callstack.compact_map do |ip| + pc = CallStack.decode_address(ip) - file, line_number, column_number = CallStack.decode_line_number(pc) + file, line_number, column_number = CallStack.decode_line_number(pc) - if file && file != "??" - next if @@skip.includes?(file) + if file && file != "??" + next if @@skip.includes?(file) - # Turn to relative to the current dir, if possible - if current_dir = CURRENT_DIR - if rel = Path[file].relative_to?(current_dir) - rel = rel.to_s - file = rel unless rel.starts_with?("..") + # Turn to relative to the current dir, if possible + if current_dir = CURRENT_DIR + if rel = Path[file].relative_to?(current_dir) + rel = rel.to_s + file = rel unless rel.starts_with?("..") + end end - end - file_line_column = file - unless line_number == 0 - file_line_column = "#{file_line_column}:#{line_number}" - file_line_column = "#{file_line_column}:#{column_number}" unless column_number == 0 + file_line_column = file + unless line_number == 0 + file_line_column = "#{file_line_column}:#{line_number}" + file_line_column = "#{file_line_column}:#{column_number}" unless column_number == 0 + end end - end - if name = CallStack.decode_function_name(pc) - function = name - elsif frame = CallStack.decode_frame(ip) - _, function, file = frame - # Crystal methods (their mangled name) start with `*`, so - # we remove that to have less clutter in the output. - function = function.lchop('*') - else - function = "??" - end - - if file_line_column - if show_full_info && (frame = CallStack.decode_frame(ip)) - _, sname, _ = frame - line = "#{file_line_column} in '#{sname}'" + if name = CallStack.decode_function_name(pc) + function = name + elsif frame = CallStack.decode_frame(ip) + _, function, file = frame + # Crystal methods (their mangled name) start with `*`, so + # we remove that to have less clutter in the output. + function = function.lchop('*') else - line = "#{file_line_column} in '#{function}'" + function = "??" end - else - if file == "??" && function == "??" - line = "???" + + if file_line_column + if show_full_info && (frame = CallStack.decode_frame(ip)) + _, sname, _ = frame + line = "#{file_line_column} in '#{sname}'" + else + line = "#{file_line_column} in '#{function}'" + end else - line = "#{file} in '#{function}'" + if file == "??" && function == "??" + line = "???" + else + line = "#{file} in '#{function}'" + end end - end - if show_full_info - line = "#{line} at 0x#{ip.address.to_s(16)}" - end + if show_full_info + line = "#{line} at 0x#{ip.address.to_s(16)}" + end - line - end + line + end + {% end %} end end diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr index 8c7bb45b3219..cdedae66dd42 100644 --- a/src/exception/call_stack/elf.cr +++ b/src/exception/call_stack/elf.cr @@ -1,5 +1,7 @@ require "crystal/elf" -require "c/link" +{% unless flag?(:wasm32) %} + require "c/link" +{% end %} struct Exception::CallStack protected def self.load_debug_info_impl diff --git a/src/exception/call_stack/null.cr b/src/exception/call_stack/null.cr index 6b9d2ab6820f..e00cec4943e8 100644 --- a/src/exception/call_stack/null.cr +++ b/src/exception/call_stack/null.cr @@ -10,4 +10,12 @@ struct Exception::CallStack def self.decode_function_name(pc) nil end + + protected def self.decode_frame(pc) + nil + end + + protected def self.unwind : Array(Void*) + [] of Void* + end end diff --git a/src/fiber/context/wasm32.cr b/src/fiber/context/wasm32.cr new file mode 100644 index 000000000000..0d8372d3e325 --- /dev/null +++ b/src/fiber/context/wasm32.cr @@ -0,0 +1,15 @@ +{% skip_file unless flag?(:wasm32) %} + +class Fiber + # :nodoc: + def makecontext(stack_ptr, fiber_main) + # TODO: Implement this using Binaryen Asyncify + end + + # :nodoc: + @[NoInline] + @[Naked] + def self.swapcontext(current_context, new_context) : Nil + # TODO: Implement this using Binaryen Asyncify + end +end diff --git a/src/gc.cr b/src/gc.cr index 238c99c1b574..4bf29345a6b6 100644 --- a/src/gc.cr +++ b/src/gc.cr @@ -98,7 +98,7 @@ module GC end end -{% if flag?(:gc_none) %} +{% if flag?(:gc_none) || flag?(:wasm32) %} require "gc/none" {% else %} require "gc/boehm" diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index a5ed138d4151..be143e1f4afb 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -93,7 +93,7 @@ lib LibGC fun size = GC_size(addr : Void*) : LibC::SizeT - {% unless flag?(:win32) %} + {% unless flag?(:win32) || flag?(:wasm32) %} # Boehm GC requires to use GC_pthread_create and GC_pthread_join instead of pthread_create and pthread_join fun pthread_create = GC_pthread_create(thread : LibC::PthreadT*, attr : LibC::PthreadAttrT*, start : Void* -> Void*, arg : Void*) : LibC::Int fun pthread_join = GC_pthread_join(thread : LibC::PthreadT, value : Void**) : LibC::Int diff --git a/src/gc/none.cr b/src/gc/none.cr index 99ffa9d30b62..1937322fb488 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -60,7 +60,7 @@ module GC reclaimed_bytes_before_gc: zero) end - {% unless flag?(:win32) %} + {% unless flag?(:win32) || flag?(:wasm32) %} # :nodoc: def self.pthread_create(thread : LibC::PthreadT*, attr : LibC::PthreadAttrT*, start : Void* -> Void*, arg : Void*) LibC.pthread_create(thread, attr, start, arg) diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 240bfab1084f..d6a57a72fb9c 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -25,7 +25,7 @@ class IO::FileDescriptor < IO end end - unless blocking || {{flag?(:win32)}} + unless blocking || {{ flag?(:win32) || flag?(:wasi) }} self.blocking = false end end diff --git a/src/kernel.cr b/src/kernel.cr index 94215527f753..9f63d2ffc678 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -502,7 +502,7 @@ def abort(message = nil, status = 1) : NoReturn exit status end -{% unless flag?(:preview_mt) %} +{% unless flag?(:preview_mt) || flag?(:wasm32) %} class Process # :nodoc: # @@ -523,7 +523,7 @@ end end {% end %} -{% unless flag?(:interpreted) %} +{% unless flag?(:interpreted) || flag?(:wasm32) %} {% unless flag?(:win32) %} # Background loop to cleanup unused fiber stacks. spawn(name: "Fiber Clean Loop") do diff --git a/src/lib_c/wasm32-wasi/c/arpa/inet.cr b/src/lib_c/wasm32-wasi/c/arpa/inet.cr new file mode 100644 index 000000000000..afac8795f66f --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/arpa/inet.cr @@ -0,0 +1,9 @@ +require "../netinet/in" +require "../stdint" + +lib LibC + fun htons(x0 : UInt16T) : UInt16T + fun ntohs(x0 : UInt16T) : UInt16T + fun inet_ntop(x0 : Int, x1 : Void*, x2 : Char*, x3 : SocklenT) : Char* + fun inet_pton(x0 : Int, x1 : Char*, x2 : Void*) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/errno.cr b/src/lib_c/wasm32-wasi/c/errno.cr new file mode 100644 index 000000000000..662c355ec1d3 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/errno.cr @@ -0,0 +1,79 @@ +lib LibC + E2BIG = 1_u16 + EACCES = 2_u16 + EADDRINUSE = 3_u16 + EADDRNOTAVAIL = 4_u16 + EAFNOSUPPORT = 5_u16 + EAGAIN = 6_u16 + EALREADY = 7_u16 + EBADF = 8_u16 + EBADMSG = 9_u16 + EBUSY = 10_u16 + ECANCELED = 11_u16 + ECHILD = 12_u16 + ECONNABORTED = 13_u16 + ECONNREFUSED = 14_u16 + ECONNRESET = 15_u16 + EDEADLK = 16_u16 + EDESTADDRREQ = 17_u16 + EDOM = 18_u16 + EDQUOT = 19_u16 + EEXIST = 20_u16 + EFAULT = 21_u16 + EFBIG = 22_u16 + EHOSTUNREACH = 23_u16 + EIDRM = 24_u16 + EILSEQ = 25_u16 + EINPROGRESS = 26_u16 + EINTR = 27_u16 + EINVAL = 28_u16 + EIO = 29_u16 + EISCONN = 30_u16 + EISDIR = 31_u16 + ELOOP = 32_u16 + EMFILE = 33_u16 + EMLINK = 34_u16 + EMSGSIZE = 35_u16 + EMULTIHOP = 36_u16 + ENAMETOOLONG = 37_u16 + ENETDOWN = 38_u16 + ENETRESET = 39_u16 + ENETUNREACH = 40_u16 + ENFILE = 41_u16 + ENOBUFS = 42_u16 + ENODEV = 43_u16 + ENOENT = 44_u16 + ENOEXEC = 45_u16 + ENOLCK = 46_u16 + ENOLINK = 47_u16 + ENOMEM = 48_u16 + ENOMSG = 49_u16 + ENOPROTOOPT = 50_u16 + ENOSPC = 51_u16 + ENOSYS = 52_u16 + ENOTCONN = 53_u16 + ENOTDIR = 54_u16 + ENOTEMPTY = 55_u16 + ENOTRECOVERABLE = 56_u16 + ENOTSOCK = 57_u16 + ENOTSUP = 58_u16 + ENOTTY = 59_u16 + ENXIO = 60_u16 + EOPNOTSUPP = LibC::ENOTSUP + EOVERFLOW = 61_u16 + EOWNERDEAD = 62_u16 + EPERM = 63_u16 + EPIPE = 64_u16 + EPROTO = 65_u16 + EPROTONOSUPPORT = 66_u16 + EPROTOTYPE = 67_u16 + ERANGE = 68_u16 + EROFS = 69_u16 + ESPIPE = 70_u16 + ESRCH = 71_u16 + ESTALE = 72_u16 + ETIMEDOUT = 73_u16 + ETXTBSY = 74_u16 + EWOULDBLOCK = LibC::EAGAIN + EXDEV = 75_u16 +end diff --git a/src/lib_c/wasm32-wasi/c/fcntl.cr b/src/lib_c/wasm32-wasi/c/fcntl.cr new file mode 100644 index 000000000000..7bbfe10f23ec --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/fcntl.cr @@ -0,0 +1,24 @@ +require "./sys/types" +require "./sys/stat" +require "./unistd" + +lib LibC + F_GETFD = 1 + F_SETFD = 2 + F_GETFL = 3 + F_SETFL = 4 + FD_CLOEXEC = 1 + O_CLOEXEC = 0 + O_CREAT = 1_u16 << 12 + O_NOFOLLOW = 0x01000000 + O_TRUNC = 8_u16 << 12 + O_APPEND = 1_u16 + O_NONBLOCK = 4_u16 + O_SYNC = 16_u16 + O_RDONLY = 0x04000000 + O_RDWR = O_RDONLY | O_WRONLY + O_WRONLY = 0x10000000 + + fun fcntl(x0 : Int, x1 : Int, ...) : Int + fun open(x0 : Char*, x1 : Int, ...) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/iconv.cr b/src/lib_c/wasm32-wasi/c/iconv.cr new file mode 100644 index 000000000000..6496b957ed24 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/iconv.cr @@ -0,0 +1,9 @@ +require "./stddef" + +lib LibC + type IconvT = Void* + + fun iconv(x0 : IconvT, x1 : Char**, x2 : SizeT*, x3 : Char**, x4 : SizeT*) : SizeT + fun iconv_close(x0 : IconvT) : Int + fun iconv_open(x0 : Char*, x1 : Char*) : IconvT +end diff --git a/src/lib_c/wasm32-wasi/c/netdb.cr b/src/lib_c/wasm32-wasi/c/netdb.cr new file mode 100644 index 000000000000..f73708969962 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/netdb.cr @@ -0,0 +1,16 @@ +require "./netinet/in" +require "./sys/socket" +require "./stdint" + +lib LibC + struct Addrinfo + ai_flags : Int + ai_family : Int + ai_socktype : Int + ai_protocol : Int + ai_addrlen : SocklenT + ai_addr : Sockaddr* + ai_canonname : Char* + ai_next : Addrinfo* + end +end diff --git a/src/lib_c/wasm32-wasi/c/netinet/in.cr b/src/lib_c/wasm32-wasi/c/netinet/in.cr new file mode 100644 index 000000000000..20a96d5ed22d --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/netinet/in.cr @@ -0,0 +1,61 @@ +require "../sys/socket" +require "../stdint" + +lib LibC + IPPROTO_IP = 0 + IPPROTO_IPV6 = 41 + IPPROTO_ICMP = 1 + IPPROTO_RAW = 255 + IPPROTO_TCP = 6 + IPPROTO_UDP = 17 + + alias InPortT = UShort + alias InAddrT = UInt + + struct InAddr + s_addr : InAddrT + end + + struct In6Addr + s6_addr : StaticArray(UChar, 16) + end + + struct SockaddrIn + sin_family : SaFamilyT + sin_port : InPortT + sin_addr : InAddr + end + + struct SockaddrIn6 + sin6_family : SaFamilyT + sin6_port : InPortT + sin6_flowinfo : UInt + sin6_addr : In6Addr + sin6_scope_id : UInt + end + + IP_MULTICAST_IF = 32 + IPV6_MULTICAST_IF = 17 + + IP_MULTICAST_TTL = 33 + IPV6_MULTICAST_HOPS = 18 + + IP_MULTICAST_LOOP = 34 + IPV6_MULTICAST_LOOP = 19 + + IP_ADD_MEMBERSHIP = 35 + IPV6_JOIN_GROUP = 20 + + IP_DROP_MEMBERSHIP = 36 + IPV6_LEAVE_GROUP = 21 + + struct IpMreq + imr_multiaddr : InAddr + imr_interface : InAddr + end + + struct Ipv6Mreq + ipv6mr_multiaddr : In6Addr + ipv6mr_interface : UInt + end +end diff --git a/src/lib_c/wasm32-wasi/c/netinet/tcp.cr b/src/lib_c/wasm32-wasi/c/netinet/tcp.cr new file mode 100644 index 000000000000..beed1db6e081 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/netinet/tcp.cr @@ -0,0 +1,6 @@ +lib LibC + TCP_NODELAY = 1 + TCP_KEEPIDLE = 4 + TCP_KEEPINTVL = 5 + TCP_KEEPCNT = 6 +end diff --git a/src/lib_c/wasm32-wasi/c/sched.cr b/src/lib_c/wasm32-wasi/c/sched.cr new file mode 100644 index 000000000000..9d83ed18504e --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/sched.cr @@ -0,0 +1,3 @@ +lib LibC + fun sched_yield : Int +end diff --git a/src/lib_c/wasm32-wasi/c/stdarg.cr b/src/lib_c/wasm32-wasi/c/stdarg.cr new file mode 100644 index 000000000000..882d4f51d35c --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/stdarg.cr @@ -0,0 +1,3 @@ +lib LibC + type VaList = Void* +end diff --git a/src/lib_c/wasm32-wasi/c/stddef.cr b/src/lib_c/wasm32-wasi/c/stddef.cr new file mode 100644 index 000000000000..4afcdf34d723 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/stddef.cr @@ -0,0 +1,3 @@ +lib LibC + alias SizeT = ULong +end diff --git a/src/lib_c/wasm32-wasi/c/stdint.cr b/src/lib_c/wasm32-wasi/c/stdint.cr new file mode 100644 index 000000000000..c3d333b17104 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/stdint.cr @@ -0,0 +1,10 @@ +lib LibC + alias Int8T = SChar + alias Int16T = Short + alias Int32T = Int + alias Int64T = LongLong + alias UInt8T = UChar + alias UInt16T = UShort + alias UInt32T = UInt + alias UInt64T = ULongLong +end diff --git a/src/lib_c/wasm32-wasi/c/stdio.cr b/src/lib_c/wasm32-wasi/c/stdio.cr new file mode 100644 index 000000000000..98967c3ddfe9 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/stdio.cr @@ -0,0 +1,9 @@ +require "./sys/types" +require "./stddef" + +lib LibC + fun dprintf(x0 : Int, x1 : Char*, ...) : Int + fun printf(x0 : Char*, ...) : Int + fun rename(x0 : Char*, x1 : Char*) : Int + fun snprintf(x0 : Char*, x1 : SizeT, x2 : Char*, ...) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/stdlib.cr b/src/lib_c/wasm32-wasi/c/stdlib.cr new file mode 100644 index 000000000000..619175b50d05 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/stdlib.cr @@ -0,0 +1,25 @@ +require "./stddef" + +lib LibC + struct DivT + quot : Int + rem : Int + end + + fun arc4random : UInt32T + fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void + fun arc4random_uniform(x0 : UInt32T) : UInt32T + fun atof(x0 : Char*) : Double + fun div(x0 : Int, x1 : Int) : DivT + fun exit(x0 : Int) : NoReturn + fun free(ptr : Void*) : Void + fun getenv(x0 : Char*) : Char* + fun malloc(size : SizeT) : Void* + fun putenv(x0 : Char*) : Int + fun realloc(ptr : Void*, size : SizeT) : Void* + fun setenv(x0 : Char*, x1 : Char*, x2 : Int) : Int + fun strtod(x0 : Char*, x1 : Char**) : Double + fun strtof(x0 : Char*, x1 : Char**) : Float + fun strtol(x0 : Char*, x1 : Char**, x2 : Int) : Long + fun unsetenv(x0 : Char*) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/string.cr b/src/lib_c/wasm32-wasi/c/string.cr new file mode 100644 index 000000000000..5be77e03cf1c --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/string.cr @@ -0,0 +1,9 @@ +require "./stddef" + +lib LibC + fun memchr(x0 : Void*, x1 : Int, x2 : SizeT) : Void* + fun memcmp(x0 : Void*, x1 : Void*, x2 : SizeT) : Int + fun strcmp(x0 : Char*, x1 : Char*) : Int + fun strerror(x0 : Int) : Char* + fun strlen(x0 : Char*) : ULong +end diff --git a/src/lib_c/wasm32-wasi/c/sys/file.cr b/src/lib_c/wasm32-wasi/c/sys/file.cr new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/lib_c/wasm32-wasi/c/sys/resource.cr b/src/lib_c/wasm32-wasi/c/sys/resource.cr new file mode 100644 index 000000000000..655a9009d3a9 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/sys/resource.cr @@ -0,0 +1,11 @@ +lib LibC + RUSAGE_SELF = 1 + RUSAGE_CHILDREN = 2 + + struct Rusage + ru_utime : Timeval + ru_stime : Timeval + end + + fun getrusage(who : Int, usage : Rusage*) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/sys/select.cr b/src/lib_c/wasm32-wasi/c/sys/select.cr new file mode 100644 index 000000000000..ef8a031287a7 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/sys/select.cr @@ -0,0 +1,13 @@ +require "./types" +require "./time" +require "../time" +require "../signal" + +lib LibC + struct FdSet + __nfds : SizeT + __fds : StaticArray(Int, 1024) + end + + fun select(x0 : Int, x1 : FdSet*, x2 : FdSet*, x3 : FdSet*, x4 : Timeval*) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/sys/socket.cr b/src/lib_c/wasm32-wasi/c/sys/socket.cr new file mode 100644 index 000000000000..82bcec46e9cc --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/sys/socket.cr @@ -0,0 +1,38 @@ +require "./types" + +lib LibC + SOCK_DGRAM = 5_u8 + SOCK_STREAM = 6_u8 + SOL_SOCKET = 0x7fffffff + AF_INET = 1 + AF_INET6 = 2 + AF_UNIX = 3 + AF_UNSPEC = 0 + SHUT_RD = 1_u8 + SHUT_RDWR = SHUT_RD | SHUT_WR + SHUT_WR = 2_u8 + SOCK_CLOEXEC = 0x00002000 + + alias SocklenT = UInt + alias SaFamilyT = UShort + + struct Sockaddr + sa_family : SaFamilyT + sa_data : StaticArray(Char, 0) + end + + struct SockaddrStorage + ss_family : SaFamilyT + __ss_data : StaticArray(Char, 32) + end + + struct Linger + l_onoff : Int + l_linger : Int + end + + fun getsockopt(x0 : Int, x1 : Int, x2 : Int, x3 : Void*, x4 : SocklenT*) : Int + fun recv(x0 : Int, x1 : Void*, x2 : SizeT, x3 : Int) : SSizeT + fun send(x0 : Int, x1 : Void*, x2 : SizeT, x3 : Int) : SSizeT + fun shutdown(x0 : Int, x1 : Int) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/sys/stat.cr b/src/lib_c/wasm32-wasi/c/sys/stat.cr new file mode 100644 index 000000000000..5b40bb7850bd --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/sys/stat.cr @@ -0,0 +1,51 @@ +require "./types" +require "../time" + +lib LibC + S_IFMT = S_IFBLK | S_IFCHR | S_IFDIR | S_IFIFO | S_IFLNK | S_IFREG | S_IFSOCK + S_IFBLK = 0x6000 + S_IFCHR = 0x2000 + S_IFIFO = 0xc000 + S_IFREG = 0x8000 + S_IFDIR = 0x4000 + S_IFLNK = 0xa000 + S_IFSOCK = 0xc000 + S_IRUSR = 0x100 + S_IWUSR = 0x80 + S_IXUSR = 0x40 + S_IRWXU = S_IXUSR | S_IWUSR | S_IRUSR + S_IRGRP = 0x20 + S_IWGRP = 0x10 + S_IXGRP = 0x8 + S_IRWXG = S_IXGRP | S_IWGRP | S_IRGRP + S_IROTH = 0x4 + S_IWOTH = 0x2 + S_IXOTH = 0x1 + S_IRWXO = S_IXOTH | S_IWOTH | S_IROTH + S_ISUID = 0x800 + S_ISGID = 0x400 + S_ISVTX = 0x200 + + struct Stat + st_dev : DevT + st_ino : InoT + st_nlink : NlinkT + st_mode : ModeT + st_uid : UidT + st_gid : GidT + __pad0 : UInt + st_rdev : DevT + st_size : OffT + st_blksize : BlksizeT + st_blocks : BlkcntT + st_atim : Timespec + st_mtim : Timespec + st_ctim : Timespec + __reserved : StaticArray(LongLong, 3) + end + + fun fstat(x0 : Int, x1 : Stat*) : Int + fun lstat(x0 : Char*, x1 : Stat*) : Int + fun mkdir(x0 : Char*, x1 : ModeT) : Int + fun stat(x0 : Char*, x1 : Stat*) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/sys/time.cr b/src/lib_c/wasm32-wasi/c/sys/time.cr new file mode 100644 index 000000000000..2f0678b910c8 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/sys/time.cr @@ -0,0 +1,15 @@ +require "./types" + +lib LibC + struct Timeval + tv_sec : TimeT + tv_usec : SusecondsT + end + + struct Timezone + tz_minuteswest : Int + tz_dsttime : Int + end + + fun gettimeofday(x0 : Timeval*, x1 : Void*) : Int +end diff --git a/src/lib_c/wasm32-wasi/c/sys/times.cr b/src/lib_c/wasm32-wasi/c/sys/times.cr new file mode 100644 index 000000000000..c190774559f4 --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/sys/times.cr @@ -0,0 +1,12 @@ +require "./types" + +lib LibC + struct Tms + tms_utime : ClockT + tms_stime : ClockT + tms_cutime : ClockT + tms_cstime : ClockT + end + + fun times(x0 : Tms*) : ClockT +end diff --git a/src/lib_c/wasm32-wasi/c/sys/types.cr b/src/lib_c/wasm32-wasi/c/sys/types.cr new file mode 100644 index 000000000000..5cca3a65b89b --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/sys/types.cr @@ -0,0 +1,61 @@ +require "../stddef" +require "../stdint" + +lib LibC + alias BlkcntT = LongLong + alias BlksizeT = Long + alias ClockT = LongLong + alias ClockidT = Int + alias DevT = ULongLong + alias GidT = UInt + alias IdT = UInt + alias InoT = ULongLong + alias ModeT = UInt + alias NlinkT = ULongLong + alias OffT = LongLong + alias PidT = Int + + union PthreadAttrTU + __i : StaticArray(Int, 14) + __vi : StaticArray(Int, 14) + __s : StaticArray(ULong, 7) + end + + struct PthreadAttrT + __u : PthreadAttrTU + end + + union PthreadCondTU + __i : StaticArray(Int, 12) + __vi : StaticArray(Int, 12) + __p : StaticArray(Void*, 6) + end + + struct PthreadCondT + __u : PthreadCondTU + end + + struct PthreadCondattrT + __attr : UInt + end + + union PthreadMutexTU + __i : StaticArray(Int, 10) + __vi : StaticArray(Int, 10) + __p : StaticArray(Void*, 5) + end + + struct PthreadMutexT + __u : PthreadMutexTU + end + + struct PthreadMutexattrT + __attr : UInt + end + + type PthreadT = Void* + alias SSizeT = Long + alias SusecondsT = LongLong + alias TimeT = LongLong + alias UidT = UInt +end diff --git a/src/lib_c/wasm32-wasi/c/termios.cr b/src/lib_c/wasm32-wasi/c/termios.cr new file mode 100644 index 000000000000..be54a30c3caf --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/termios.cr @@ -0,0 +1,120 @@ +require "./sys/types" + +lib LibC + VEOF = 4 + VEOL = 11 + VERASE = 2 + VINTR = 0 + VKILL = 3 + VMIN = 6 + VQUIT = 1 + VSTART = 8 + VSTOP = 9 + VSUSP = 10 + BRKINT = 0o000002 + ICRNL = 0o000400 + IGNBRK = 0o000001 + IGNCR = 0o000200 + IGNPAR = 0o000004 + INLCR = 0o000100 + INPCK = 0o000020 + ISTRIP = 0o000040 + IXANY = 0o004000 + IXOFF = 0o010000 + IXON = 0o002000 + PARMRK = 0o000010 + OPOST = 0o000001 + ONLCR = 0o000004 + OCRNL = 0o000010 + ONOCR = 0o000020 + ONLRET = 0o000040 + OFDEL = 0o000200 + OFILL = 0o000100 + CRDLY = 0o003000 + CR0 = 0o000000 + CR1 = 0o001000 + CR2 = 0o002000 + CR3 = 0o003000 + TABDLY = 0o014000 + TAB0 = 0o000000 + TAB1 = 0o004000 + TAB2 = 0o010000 + TAB3 = 0o014000 + BSDLY = 0o020000 + BS0 = 0o000000 + BS1 = 0o020000 + VTDLY = 0o040000 + VT0 = 0o000000 + VT1 = 0o040000 + FFDLY = 0o100000 + FF0 = 0o000000 + FF1 = 0o100000 + NLDLY = 0o000400 + NL0 = 0o000000 + NL1 = 0o000400 + B0 = 0o000000 + B50 = 0o000001 + B75 = 0o000002 + B110 = 0o000003 + B134 = 0o000004 + B150 = 0o000005 + B200 = 0o000006 + B300 = 0o000007 + B600 = 0o000010 + B1200 = 0o000011 + B1800 = 0o000012 + B2400 = 0o000013 + B4800 = 0o000014 + B9600 = 0o000015 + B19200 = 0o000016 + B38400 = 0o000017 + CSIZE = 0o000060 + CS5 = 0o000000 + CS6 = 0o000020 + CS7 = 0o000040 + CS8 = 0o000060 + CSTOPB = 0o000100 + CREAD = 0o000200 + PARENB = 0o000400 + PARODD = 0o001000 + HUPCL = 0o002000 + CLOCAL = 0o004000 + ECHO = 0o000010 + ECHOE = 0o000020 + ECHOK = 0o000040 + ECHONL = 0o000100 + ICANON = 0o000002 + IEXTEN = 0o100000 + ISIG = 0o000001 + NOFLSH = 0o000200 + TOSTOP = 0o000400 + TCSANOW = 0 + TCSADRAIN = 1 + TCSAFLUSH = 2 + TCIFLUSH = 0 + TCIOFLUSH = 2 + TCOFLUSH = 1 + TCIOFF = 2 + TCION = 3 + TCOOFF = 0 + TCOON = 1 + + alias CcT = UInt8 + alias SpeedT = UInt + alias TcflagT = UInt + + struct Termios + c_iflag : TcflagT + c_oflag : TcflagT + c_cflag : TcflagT + c_lflag : TcflagT + c_line : CcT + c_cc : StaticArray(CcT, 32) + __c_ispeed : SpeedT + __c_ospeed : SpeedT + end + + fun tcgetattr(x0 : Int, x1 : Termios*) : Int + fun tcsetattr(x0 : Int, x1 : Int, x2 : Termios*) : Int + fun cfmakeraw(x0 : Termios*) : Void +end diff --git a/src/lib_c/wasm32-wasi/c/time.cr b/src/lib_c/wasm32-wasi/c/time.cr new file mode 100644 index 000000000000..6b8fc35a21eb --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/time.cr @@ -0,0 +1,32 @@ +require "./sys/types" + +lib LibC + CLOCK_MONOTONIC = 1 + CLOCK_REALTIME = 0 + + struct Tm + tm_sec : Int + tm_min : Int + tm_hour : Int + tm_mday : Int + tm_mon : Int + tm_year : Int + tm_wday : Int + tm_yday : Int + tm_isdst : Int + tm_gmtoff : Int + tm_zone : Char* + __tm_nsec : Int + end + + struct Timespec + tv_sec : TimeT + tv_nsec : Long + end + + fun clock_gettime(x0 : ClockidT, x1 : Timespec*) : Int + fun gmtime_r(x0 : TimeT*, x1 : Tm*) : Tm* + fun localtime_r(x0 : TimeT*, x1 : Tm*) : Tm* + fun mktime(x0 : Tm*) : TimeT + fun timegm(x0 : Tm*) : TimeT +end diff --git a/src/lib_c/wasm32-wasi/c/unistd.cr b/src/lib_c/wasm32-wasi/c/unistd.cr new file mode 100644 index 000000000000..7b4a5949cafd --- /dev/null +++ b/src/lib_c/wasm32-wasi/c/unistd.cr @@ -0,0 +1,32 @@ +require "./sys/types" +require "./stdint" + +lib LibC + F_OK = 0 + R_OK = 1 + W_OK = 2 + X_OK = 4 + SC_CLK_TCK = 2 + SC_NPROCESSORS_ONLN = 84 + + fun access(x0 : Char*, x1 : Int) : Int + fun chdir(x0 : Char*) : Int + fun close(fd : Int) : Int + fun _exit(x0 : Int) : NoReturn + fun fdatasync(x0 : Int) : Int + fun fsync(x0 : Int) : Int + fun ftruncate(x0 : Int, x1 : OffT) : Int + fun getcwd(x0 : Char*, x1 : SizeT) : Char* + fun isatty(x0 : Int) : Int + fun link(x0 : Char*, x1 : Char*) : Int + fun lseek(x0 : Int, x1 : OffT, x2 : Int) : OffT + fun read(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT + fun pread(x0 : Int, x1 : Void*, x2 : SizeT, x3 : OffT) : SSizeT + fun readlink(x0 : Char*, x1 : Char*, x2 : SizeT) : SSizeT + fun rmdir(x0 : Char*) : Int + fun sleep(x0 : UInt) : UInt + fun symlink(x0 : Char*, x1 : Char*) : Int + fun sysconf(x0 : Int) : Long + fun unlink(x0 : Char*) : Int + fun write(x0 : Int, x1 : Void*, x2 : SizeT) : SSizeT +end diff --git a/src/llvm.cr b/src/llvm.cr index 9f0c388603b8..ab36cc57d966 100644 --- a/src/llvm.cr +++ b/src/llvm.cr @@ -55,6 +55,23 @@ module LLVM {% end %} end + def self.init_webassembly : Nil + return if @@initialized_webassembly + @@initialized_webassembly = true + + {% if LibLLVM::BUILT_TARGETS.includes?(:webassembly) %} + LibLLVM.initialize_webassembly_target_info + LibLLVM.initialize_webassembly_target + LibLLVM.initialize_webassembly_target_mc + LibLLVM.initialize_webassembly_asm_printer + LibLLVM.initialize_webassembly_asm_parser + # LibLLVM.link_in_jit + LibLLVM.link_in_mc_jit + {% else %} + raise "ERROR: LLVM was built without WebAssembly target" + {% end %} + end + def self.start_multithreaded : Bool if multithreaded? true diff --git a/src/llvm/abi/wasm32.cr b/src/llvm/abi/wasm32.cr new file mode 100644 index 000000000000..1882a44c8c4a --- /dev/null +++ b/src/llvm/abi/wasm32.cr @@ -0,0 +1,44 @@ +require "../abi" + +class LLVM::ABI::Wasm32 < LLVM::ABI + def abi_info(atys : Array(Type), rty : Type, ret_def : Bool, context : Context) + ret_ty = compute_return_type(rty, ret_def, context) + arg_tys = compute_arg_types(atys, context) + FunctionType.new(arg_tys, ret_ty) + end + + def align(type : Type) + target_data.abi_alignment(type).to_i32 + end + + def size(type : Type) + target_data.abi_size(type).to_i32 + end + + private def aggregate?(type) + case type.kind + when .struct?, .array? + true + else + false + end + end + + private def compute_return_type(rty, ret_def, context) + if aggregate?(rty) + ArgType.indirect(rty, LLVM::Attribute::ByVal) + else + ArgType.direct(rty) + end + end + + private def compute_arg_types(atys, context) + atys.map do |t| + if aggregate?(t) + ArgType.indirect(t, LLVM::Attribute::ByVal) + else + ArgType.direct(t) + end + end + end +end diff --git a/src/llvm/lib_llvm.cr b/src/llvm/lib_llvm.cr index f633a9cf8277..f4a384e329df 100644 --- a/src/llvm/lib_llvm.cr +++ b/src/llvm/lib_llvm.cr @@ -225,6 +225,11 @@ lib LibLLVM fun initialize_arm_target = LLVMInitializeARMTarget fun initialize_arm_target_info = LLVMInitializeARMTargetInfo fun initialize_arm_target_mc = LLVMInitializeARMTargetMC + fun initialize_webassembly_asm_printer = LLVMInitializeWebAssemblyAsmPrinter + fun initialize_webassembly_asm_parser = LLVMInitializeWebAssemblyAsmParser + fun initialize_webassembly_target = LLVMInitializeWebAssemblyTarget + fun initialize_webassembly_target_info = LLVMInitializeWebAssemblyTargetInfo + fun initialize_webassembly_target_mc = LLVMInitializeWebAssemblyTargetMC fun initialize_native_target = LLVMInitializeNativeTarget fun is_constant = LLVMIsConstant(val : ValueRef) : Int32 fun is_function_var_arg = LLVMIsFunctionVarArg(ty : TypeRef) : Int32 diff --git a/src/llvm/target_machine.cr b/src/llvm/target_machine.cr index ee7a413df959..1b9dbf685921 100644 --- a/src/llvm/target_machine.cr +++ b/src/llvm/target_machine.cr @@ -57,6 +57,8 @@ class LLVM::TargetMachine ABI::AArch64.new(self) when /arm/ ABI::ARM.new(self) + when /wasm32/ + ABI::Wasm32.new(self) else raise "Unsupported ABI for target triple: #{triple}" end diff --git a/src/log/io_backend.cr b/src/log/io_backend.cr index cc7e38458552..9092495c8bf4 100644 --- a/src/log/io_backend.cr +++ b/src/log/io_backend.cr @@ -3,8 +3,8 @@ class Log::IOBackend < Log::Backend property io : IO property formatter : Formatter - {% if flag?(:win32) %} - # TODO: this constructor must go away once channels are fixed in Windows + {% if flag?(:win32) || flag?(:wasm32) %} + # TODO: this constructor must go away once channels are fixed in Windows / WebAssembly def initialize(@io = STDOUT, *, @formatter : Formatter = ShortFormat, dispatcher : Dispatcher::Spec = DispatchMode::Sync) super(dispatcher) end diff --git a/src/prelude.cr b/src/prelude.cr index 1b934882d374..ea88f25215f1 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -45,6 +45,7 @@ require "enumerable" require "env" require "errno" require "winerror" +require "wasi_error" require "file" require "float" require "gc" @@ -71,7 +72,7 @@ require "range" require "reference" require "regex" require "set" -{% unless flag?(:win32) %} +{% unless flag?(:win32) || flag?(:wasm32) %} require "signal" {% end %} require "slice" diff --git a/src/process/status.cr b/src/process/status.cr index 53c74006a1db..56da75515688 100644 --- a/src/process/status.cr +++ b/src/process/status.cr @@ -48,6 +48,13 @@ class Process::Status {% end %} end + {% if flag?(:wasm32) %} + # wasm32 does not define `Signal` + def exit_signal + raise NotImplementedError.new("Process::Status#exit_signal") + end + {% end %} + # If `normal_exit?` is `true`, returns the exit code of the process. def exit_code : Int32 {% if flag?(:unix) %} diff --git a/src/raise.cr b/src/raise.cr index 54d1d11157f1..30653a790662 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -170,6 +170,31 @@ end __crystal_continue_unwind end +{% elsif flag?(:wasm32) %} + # :nodoc: + fun __crystal_personality + LibC.printf("EXITING: __crystal_personality called") + LibC.exit(1) + end + + # :nodoc: + @[Raises] + fun __crystal_raise(ex : Void*) : NoReturn + LibC.printf("EXITING: __crystal_raise called") + LibC.exit(1) + end + + # :nodoc: + fun __crystal_get_exception(ex : Void*) : UInt64 + LibC.printf("EXITING: __crystal_get_exception called") + LibC.exit(1) + 0u64 + end + + def raise(exception : Exception) : NoReturn + LibC.printf("EXITING: Attempting to raise:\n#{exception.inspect_with_backtrace}") + LibC.exit(1) + end {% else %} # :nodoc: fun __crystal_personality(version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*) : LibUnwind::ReasonCode @@ -189,7 +214,7 @@ end end {% end %} -{% unless flag?(:win32) %} +{% unless flag?(:win32) || flag?(:wasm32) %} # :nodoc: @[Raises] fun __crystal_raise(unwind_ex : LibUnwind::Exception*) : NoReturn diff --git a/src/socket.cr b/src/socket.cr index f315681ee709..508f76f08e9b 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -264,55 +264,105 @@ class Socket < IO io << "#<#{self.class}:fd #{fd}>" end - def send_buffer_size : Int32 - getsockopt LibC::SO_SNDBUF, 0 - end + {% if flag?(:wasm32) %} + def send_buffer_size : Int32 + raise NotImplementedError.new "Socket#send_buffer_size" + end - def send_buffer_size=(val : Int32) - setsockopt LibC::SO_SNDBUF, val - val - end + def send_buffer_size=(val : Int32) + raise NotImplementedError.new "Socket#send_buffer_size=" + end - def recv_buffer_size : Int32 - getsockopt LibC::SO_RCVBUF, 0 - end + def recv_buffer_size : Int32 + raise NotImplementedError.new "Socket#recv_buffer_size" + end - def recv_buffer_size=(val : Int32) - setsockopt LibC::SO_RCVBUF, val - val - end + def recv_buffer_size=(val : Int32) + raise NotImplementedError.new "Socket#recv_buffer_size=" + end - def reuse_address? : Bool - getsockopt_bool LibC::SO_REUSEADDR - end + def reuse_address? : Bool + raise NotImplementedError.new "Socket#reuse_address?" + end - def reuse_address=(val : Bool) - setsockopt_bool LibC::SO_REUSEADDR, val - end + def reuse_address=(val : Bool) + raise NotImplementedError.new "Socket#reuse_address=" + end - def reuse_port? : Bool - system_reuse_port? - end + def reuse_port? : Bool + raise NotImplementedError.new "Socket#reuse_port?" + end - def reuse_port=(val : Bool) - self.system_reuse_port = val - end + def reuse_port=(val : Bool) + raise NotImplementedError.new "Socket#reuse_port=" + end - def broadcast? : Bool - getsockopt_bool LibC::SO_BROADCAST - end + def broadcast? : Bool + raise NotImplementedError.new "Socket#broadcast?" + end - def broadcast=(val : Bool) - setsockopt_bool LibC::SO_BROADCAST, val - end + def broadcast=(val : Bool) + raise NotImplementedError.new "Socket#broadcast=" + end - def keepalive? - getsockopt_bool LibC::SO_KEEPALIVE - end + def keepalive? + raise NotImplementedError.new "Socket#keepalive?" + end - def keepalive=(val : Bool) - setsockopt_bool LibC::SO_KEEPALIVE, val - end + def keepalive=(val : Bool) + raise NotImplementedError.new "Socket#keepalive=" + end + {% else %} + def send_buffer_size : Int32 + getsockopt LibC::SO_SNDBUF, 0 + end + + def send_buffer_size=(val : Int32) + setsockopt LibC::SO_SNDBUF, val + val + end + + def recv_buffer_size : Int32 + getsockopt LibC::SO_RCVBUF, 0 + end + + def recv_buffer_size=(val : Int32) + setsockopt LibC::SO_RCVBUF, val + val + end + + def reuse_address? : Bool + getsockopt_bool LibC::SO_REUSEADDR + end + + def reuse_address=(val : Bool) + setsockopt_bool LibC::SO_REUSEADDR, val + end + + def reuse_port? : Bool + system_reuse_port? + end + + def reuse_port=(val : Bool) + self.system_reuse_port = val + end + + def broadcast? : Bool + getsockopt_bool LibC::SO_BROADCAST + end + + def broadcast=(val : Bool) + setsockopt_bool LibC::SO_BROADCAST, val + end + + def keepalive? + getsockopt_bool LibC::SO_KEEPALIVE + end + + def keepalive=(val : Bool) + setsockopt_bool LibC::SO_KEEPALIVE, val + end + {% end %} def linger system_linger diff --git a/src/socket/address.cr b/src/socket/address.cr index f1279e63cf59..2ecfc32de029 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -244,6 +244,8 @@ class Socket addr.__u6_addr.__u6_addr8 {% elsif flag?(:linux) && flag?(:musl) %} addr.__in6_union.__s6_addr + {% elsif flag?(:wasm32) %} + addr.s6_addr {% elsif flag?(:linux) %} addr.__in6_u.__u6_addr8 {% elsif flag?(:win32) %} @@ -329,19 +331,31 @@ class Socket getter path : String # :nodoc: - MAX_PATH_SIZE = LibC::SockaddrUn.new.sun_path.size - 1 + MAX_PATH_SIZE = {% if flag?(:wasm32) %} + 0 + {% else %} + LibC::SockaddrUn.new.sun_path.size - 1 + {% end %} def initialize(@path : String) if @path.bytesize + 1 > MAX_PATH_SIZE raise ArgumentError.new("Path size exceeds the maximum size of #{MAX_PATH_SIZE} bytes") end @family = Family::UNIX - @size = sizeof(LibC::SockaddrUn) + @size = {% if flag?(:wasm32) %} + 1 + {% else %} + sizeof(LibC::SockaddrUn) + {% end %} end # Creates an `UNIXSocket` from the internal OS representation. def self.from(sockaddr : LibC::Sockaddr*, addrlen) : UNIXAddress - new(sockaddr.as(LibC::SockaddrUn*), addrlen.to_i) + {% if flag?(:wasm32) %} + raise NotImplementedError.new "Socket::UnixAddress.from" + {% else %} + new(sockaddr.as(LibC::SockaddrUn*), addrlen.to_i) + {% end %} end # Parses a `Socket::UNIXAddress` from an URI. @@ -383,11 +397,13 @@ class Socket parse URI.parse(uri) end - protected def initialize(sockaddr : LibC::SockaddrUn*, size) - @family = Family::UNIX - @path = String.new(sockaddr.value.sun_path.to_unsafe) - @size = size || sizeof(LibC::SockaddrUn) - end + {% unless flag?(:wasm32) %} + protected def initialize(sockaddr : LibC::SockaddrUn*, size) + @family = Family::UNIX + @path = String.new(sockaddr.value.sun_path.to_unsafe) + @size = size || sizeof(LibC::SockaddrUn) + end + {% end %} def_equals_and_hash path @@ -396,10 +412,14 @@ class Socket end def to_unsafe : LibC::Sockaddr* - sockaddr = Pointer(LibC::SockaddrUn).malloc - sockaddr.value.sun_family = family - sockaddr.value.sun_path.to_unsafe.copy_from(@path.to_unsafe, @path.bytesize + 1) - sockaddr.as(LibC::Sockaddr*) + {% if flag?(:wasm32) %} + raise NotImplementedError.new "Socket::UnixAddress#to_unsafe" + {% else %} + sockaddr = Pointer(LibC::SockaddrUn).malloc + sockaddr.value.sun_family = family + sockaddr.value.sun_path.to_unsafe.copy_from(@path.to_unsafe, @path.bytesize + 1) + sockaddr.as(LibC::Sockaddr*) + {% end %} end end diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index 9163b6c9d617..8e6a43539fae 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -129,58 +129,62 @@ class Socket end private def self.getaddrinfo(domain, service, family, type, protocol, timeout) - # RFC 3986 says: - # > When a non-ASCII registered name represents an internationalized domain name - # > intended for resolution via the DNS, the name must be transformed to the IDNA - # > encoding [RFC3490] prior to name lookup. - domain = URI::Punycode.to_ascii domain - - hints = LibC::Addrinfo.new - hints.ai_family = (family || Family::UNSPEC).to_i32 - hints.ai_socktype = type - hints.ai_protocol = protocol - hints.ai_flags = 0 - - if service.is_a?(Int) - hints.ai_flags |= LibC::AI_NUMERICSERV - end - - # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults - # if AI_NUMERICSERV is set, and servname is NULL or 0. - {% if flag?(:darwin) %} - if (service == 0 || service == nil) && (hints.ai_flags & LibC::AI_NUMERICSERV) + {% if flag?(:wasm32) %} + raise NotImplementedError.new "Socket::Addrinfo.getaddrinfo" + {% else %} + # RFC 3986 says: + # > When a non-ASCII registered name represents an internationalized domain name + # > intended for resolution via the DNS, the name must be transformed to the IDNA + # > encoding [RFC3490] prior to name lookup. + domain = URI::Punycode.to_ascii domain + + hints = LibC::Addrinfo.new + hints.ai_family = (family || Family::UNSPEC).to_i32 + hints.ai_socktype = type + hints.ai_protocol = protocol + hints.ai_flags = 0 + + if service.is_a?(Int) hints.ai_flags |= LibC::AI_NUMERICSERV - service = "00" - end - {% end %} - {% if flag?(:win32) %} - if service.is_a?(Int) && service < 0 - raise Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service) end - {% end %} - ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) - unless ret.zero? - {% if flag?(:unix) %} - # EAI_SYSTEM is not defined on win32 - if ret == LibC::EAI_SYSTEM - raise Error.from_os_error nil, Errno.value, domain: domain + # On OS X < 10.12, the libsystem implementation of getaddrinfo segfaults + # if AI_NUMERICSERV is set, and servname is NULL or 0. + {% if flag?(:darwin) %} + if (service == 0 || service == nil) && (hints.ai_flags & LibC::AI_NUMERICSERV) + hints.ai_flags |= LibC::AI_NUMERICSERV + service = "00" + end + {% end %} + {% if flag?(:win32) %} + if service.is_a?(Int) && service < 0 + raise Error.from_os_error(nil, WinError::WSATYPE_NOT_FOUND, domain: domain, type: type, protocol: protocol, service: service) end {% end %} - error = {% if flag?(:win32) %} - WinError.new(ret.to_u32!) - {% else %} - Errno.new(ret) - {% end %} - raise Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) - end + ret = LibC.getaddrinfo(domain, service.to_s, pointerof(hints), out ptr) + unless ret.zero? + {% if flag?(:unix) %} + # EAI_SYSTEM is not defined on win32 + if ret == LibC::EAI_SYSTEM + raise Error.from_os_error nil, Errno.value, domain: domain + end + {% end %} - begin - yield new(ptr) - ensure - LibC.freeaddrinfo(ptr) - end + error = {% if flag?(:win32) %} + WinError.new(ret.to_u32!) + {% else %} + Errno.new(ret) + {% end %} + raise Error.from_os_error(nil, error, domain: domain, type: type, protocol: protocol, service: service) + end + + begin + yield new(ptr) + ensure + LibC.freeaddrinfo(ptr) + end + {% end %} end # Resolves *domain* for the TCP protocol and returns an `Array` of possible diff --git a/src/socket/common.cr b/src/socket/common.cr index 2b55eb715992..19f1700a2cbd 100644 --- a/src/socket/common.cr +++ b/src/socket/common.cr @@ -1,6 +1,9 @@ {% if flag?(:win32) %} require "c/ws2tcpip" require "c/afunix" +{% elsif flag?(:wasi) %} + require "c/arpa/inet" + require "c/netinet/in" {% else %} require "c/arpa/inet" require "c/sys/un" @@ -38,10 +41,12 @@ class Socket < IO end enum Type - STREAM = LibC::SOCK_STREAM - DGRAM = LibC::SOCK_DGRAM - RAW = LibC::SOCK_RAW - SEQPACKET = LibC::SOCK_SEQPACKET + STREAM = LibC::SOCK_STREAM + DGRAM = LibC::SOCK_DGRAM + {% unless flag?(:wasi) %} + RAW = LibC::SOCK_RAW + SEQPACKET = LibC::SOCK_SEQPACKET + {% end %} end class Error < IO::Error diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index 9ec3a8188ef6..4efbd1c1c4bd 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -63,18 +63,22 @@ class UNIXSocket < Socket # left.gets # => "message" # ``` def self.pair(type : Type = Type::STREAM) : {UNIXSocket, UNIXSocket} - fds = uninitialized Int32[2] + {% if flag?(:wasm32) %} + raise NotImplementedError.new "UNIXSocket.pair" + {% else %} + fds = uninitialized Int32[2] - socktype = type.value - {% if LibC.has_constant?(:SOCK_CLOEXEC) %} + socktype = type.value + {% if LibC.has_constant?(:SOCK_CLOEXEC) %} socktype |= LibC::SOCK_CLOEXEC - {% end %} + {% end %} - if LibC.socketpair(Family::UNIX, socktype, 0, fds) != 0 - raise Socket::Error.new("socketpair:") - end + if LibC.socketpair(Family::UNIX, socktype, 0, fds) != 0 + raise Socket::Error.new("socketpair:") + end - {UNIXSocket.new(fd: fds[0], type: type), UNIXSocket.new(fd: fds[1], type: type)} + {UNIXSocket.new(fd: fds[0], type: type), UNIXSocket.new(fd: fds[1], type: type)} + {% end %} end # Creates a pair of unnamed UNIX sockets (see `pair`) and yields them to the diff --git a/src/spec.cr b/src/spec.cr index 517acc720a58..5d370e71811a 100644 --- a/src/spec.cr +++ b/src/spec.cr @@ -127,7 +127,7 @@ end Spec.add_split_filter ENV["SPEC_SPLIT"]? -{% unless flag?(:win32) %} +{% unless flag?(:win32) || flag?(:wasm32) %} # TODO(windows): re-enable this once Signal is ported Signal::INT.trap { Spec.abort! } {% end %} diff --git a/src/system_error.cr b/src/system_error.cr index f7e4653cc27a..7d434695b003 100644 --- a/src/system_error.cr +++ b/src/system_error.cr @@ -43,7 +43,7 @@ module SystemError end # The original system error wrapped by this exception - getter os_error : Errno | WinError | Nil + getter os_error : Errno | WinError | WasiError | Nil # :nodoc: protected def os_error=(@os_error) @@ -54,7 +54,7 @@ module SystemError # # The system message corresponding to the OS error value amends the *message*. # Additional keyword arguments are forwarded to the exception initializer `.new_from_os_error`. - def from_os_error(message : String?, os_error : Errno | WinError | Nil, **opts) + def from_os_error(message : String?, os_error : Errno | WinError | WasiError | Nil, **opts) message = self.build_message(message, **opts) message = if message @@ -94,7 +94,7 @@ module SystemError # By default it returns the result of `Errno#message` or `WinError#message`. # This method can be overridden for customization of the error message based # on *or_error* and *\*\*opts*. - protected def os_error_message(os_error : Errno | WinError | Nil, **opts) : String? + protected def os_error_message(os_error : Errno | WinError | WasiError | Nil, **opts) : String? os_error.try &.message end diff --git a/src/wasi_error.cr b/src/wasi_error.cr new file mode 100644 index 000000000000..9a04ed315463 --- /dev/null +++ b/src/wasi_error.cr @@ -0,0 +1,249 @@ +enum WasiError : UInt16 + # Returns the system error message associated with this error code. + def message : String + case self + when SUCCESS then "No error occurred. System call completed successfully." + when TOOBIG then "Argument list too long." + when ACCES then "Permission denied." + when ADDRINUSE then "Address in use." + when ADDRNOTAVAIL then "Address not available." + when AFNOSUPPORT then "Address family not supported." + when AGAIN then "Resource unavailable, or operation would block." + when ALREADY then "Connection already in progress." + when BADF then "Bad file descriptor." + when BADMSG then "Bad message." + when BUSY then "Device or resource busy." + when CANCELED then "Operation canceled." + when CHILD then "No child processes." + when CONNABORTED then "Connection aborted." + when CONNREFUSED then "Connection refused." + when CONNRESET then "Connection reset." + when DEADLK then "Resource deadlock would occur." + when DESTADDRREQ then "Destination address required." + when DOM then "Mathematics argument out of domain of function." + when DQUOT then "Reserved." + when EXIST then "File exists." + when FAULT then "Bad address." + when FBIG then "File too large." + when HOSTUNREACH then "Host is unreachable." + when IDRM then "Identifier removed." + when ILSEQ then "Illegal byte sequence." + when INPROGRESS then "Operation in progress." + when INTR then "Interrupted function." + when INVAL then "Invalid argument." + when IO then "I/O error." + when ISCONN then "Socket is connected." + when ISDIR then "Is a directory." + when LOOP then "Too many levels of symbolic links." + when MFILE then "File descriptor value too large." + when MLINK then "Too many links." + when MSGSIZE then "Message too large." + when MULTIHOP then "Reserved." + when NAMETOOLONG then "Filename too long." + when NETDOWN then "Network is down." + when NETRESET then "Connection aborted by network." + when NETUNREACH then "Network unreachable." + when NFILE then "Too many files open in system." + when NOBUFS then "No buffer space available." + when NODEV then "No such device." + when NOENT then "No such file or directory." + when NOEXEC then "Executable file format error." + when NOLCK then "No locks available." + when NOLINK then "Reserved." + when NOMEM then "Not enough space." + when NOMSG then "No message of the desired type." + when NOPROTOOPT then "Protocol not available." + when NOSPC then "No space left on device." + when NOSYS then "Function not supported." + when NOTCONN then "The socket is not connected." + when NOTDIR then "Not a directory or a symbolic link to a directory." + when NOTEMPTY then "Directory not empty." + when NOTRECOVERABLE then "State not recoverable." + when NOTSOCK then "Not a socket." + when NOTSUP then "Not supported, or operation not supported on socket." + when NOTTY then "Inappropriate I/O control operation." + when NXIO then "No such device or address." + when OVERFLOW then "Value too large to be stored in data type." + when OWNERDEAD then "Previous owner died." + when PERM then "Operation not permitted." + when PIPE then "Broken pipe." + when PROTO then "Protocol error." + when PROTONOSUPPORT then "Protocol not supported." + when PROTOTYPE then "Protocol wrong type for socket." + when RANGE then "Result too large." + when ROFS then "Read-only file system." + when SPIPE then "Invalid seek." + when SRCH then "No such process." + when STALE then "Reserved." + when TIMEDOUT then "Connection timed out." + when TXTBSY then "Text file busy." + when XDEV then "Cross-device link." + when NOTCAPABLE then "Extension: Capabilities insufficient." + else "Unknown error." + end + end + + # Transforms this `WasiError` value to the equivalent `Errno` value. + # + # This is only defined for some values. If no transformation is defined for + # a specific value, the default result is `Errno::EINVAL`. + def to_errno : Errno + case self + when TOOBIG then Errno::E2BIG + when ACCES then Errno::EACCES + when ADDRINUSE then Errno::EADDRINUSE + when ADDRNOTAVAIL then Errno::EADDRNOTAVAIL + when AFNOSUPPORT then Errno::EAFNOSUPPORT + when AGAIN then Errno::EAGAIN + when ALREADY then Errno::EALREADY + when BADF then Errno::EBADF + when BADMSG then Errno::EBADMSG + when BUSY then Errno::EBUSY + when CANCELED then Errno::ECANCELED + when CHILD then Errno::ECHILD + when CONNABORTED then Errno::ECONNABORTED + when CONNREFUSED then Errno::ECONNREFUSED + when CONNRESET then Errno::ECONNRESET + when DEADLK then Errno::EDEADLK + when DESTADDRREQ then Errno::EDESTADDRREQ + when DOM then Errno::EDOM + when DQUOT then Errno::EDQUOT + when EXIST then Errno::EEXIST + when FAULT then Errno::EFAULT + when FBIG then Errno::EFBIG + when HOSTUNREACH then Errno::EHOSTUNREACH + when IDRM then Errno::EIDRM + when ILSEQ then Errno::EILSEQ + when INPROGRESS then Errno::EINPROGRESS + when INTR then Errno::EINTR + when INVAL then Errno::EINVAL + when IO then Errno::EIO + when ISCONN then Errno::EISCONN + when ISDIR then Errno::EISDIR + when LOOP then Errno::ELOOP + when MFILE then Errno::EMFILE + when MLINK then Errno::EMLINK + when MSGSIZE then Errno::EMSGSIZE + when MULTIHOP then Errno::EMULTIHOP + when NAMETOOLONG then Errno::ENAMETOOLONG + when NETDOWN then Errno::ENETDOWN + when NETRESET then Errno::ENETRESET + when NETUNREACH then Errno::ENETUNREACH + when NFILE then Errno::ENFILE + when NOBUFS then Errno::ENOBUFS + when NODEV then Errno::ENODEV + when NOENT then Errno::ENOENT + when NOEXEC then Errno::ENOEXEC + when NOLCK then Errno::ENOLCK + when NOLINK then Errno::ENOLINK + when NOMEM then Errno::ENOMEM + when NOMSG then Errno::ENOMSG + when NOPROTOOPT then Errno::ENOPROTOOPT + when NOSPC then Errno::ENOSPC + when NOSYS then Errno::ENOSYS + when NOTCONN then Errno::ENOTCONN + when NOTDIR then Errno::ENOTDIR + when NOTEMPTY then Errno::ENOTEMPTY + when NOTRECOVERABLE then Errno::ENOTRECOVERABLE + when NOTSOCK then Errno::ENOTSOCK + when NOTSUP then Errno::ENOTSUP + when NOTTY then Errno::ENOTTY + when NXIO then Errno::ENXIO + when OVERFLOW then Errno::EOVERFLOW + when OWNERDEAD then Errno::EOWNERDEAD + when PERM then Errno::EPERM + when PIPE then Errno::EPIPE + when PROTO then Errno::EPROTO + when PROTONOSUPPORT then Errno::EPROTONOSUPPORT + when PROTOTYPE then Errno::EPROTOTYPE + when RANGE then Errno::ERANGE + when ROFS then Errno::EROFS + when SPIPE then Errno::ESPIPE + when SRCH then Errno::ESRCH + when STALE then Errno::ESTALE + when TIMEDOUT then Errno::ETIMEDOUT + when TXTBSY then Errno::ETXTBSY + when XDEV then Errno::EXDEV + when NOTCAPABLE then Errno::ENOTCAPABLE + else Errno::EINVAL + end + end + + SUCCESS # No error occurred. System call completed successfully. + TOOBIG # Argument list too long. + ACCES # Permission denied. + ADDRINUSE # Address in use. + ADDRNOTAVAIL # Address not available. + AFNOSUPPORT # Address family not supported. + AGAIN # Resource unavailable, or operation would block. + ALREADY # Connection already in progress. + BADF # Bad file descriptor. + BADMSG # Bad message. + BUSY # Device or resource busy. + CANCELED # Operation canceled. + CHILD # No child processes. + CONNABORTED # Connection aborted. + CONNREFUSED # Connection refused. + CONNRESET # Connection reset. + DEADLK # Resource deadlock would occur. + DESTADDRREQ # Destination address required. + DOM # Mathematics argument out of domain of function. + DQUOT # Reserved. + EXIST # File exists. + FAULT # Bad address. + FBIG # File too large. + HOSTUNREACH # Host is unreachable. + IDRM # Identifier removed. + ILSEQ # Illegal byte sequence. + INPROGRESS # Operation in progress. + INTR # Interrupted function. + INVAL # Invalid argument. + IO # I/O error. + ISCONN # Socket is connected. + ISDIR # Is a directory. + LOOP # Too many levels of symbolic links. + MFILE # File descriptor value too large. + MLINK # Too many links. + MSGSIZE # Message too large. + MULTIHOP # Reserved. + NAMETOOLONG # Filename too long. + NETDOWN # Network is down. + NETRESET # Connection aborted by network. + NETUNREACH # Network unreachable. + NFILE # Too many files open in system. + NOBUFS # No buffer space available. + NODEV # No such device. + NOENT # No such file or directory. + NOEXEC # Executable file format error. + NOLCK # No locks available. + NOLINK # Reserved. + NOMEM # Not enough space. + NOMSG # No message of the desired type. + NOPROTOOPT # Protocol not available. + NOSPC # No space left on device. + NOSYS # Function not supported. + NOTCONN # The socket is not connected. + NOTDIR # Not a directory or a symbolic link to a directory. + NOTEMPTY # Directory not empty. + NOTRECOVERABLE # State not recoverable. + NOTSOCK # Not a socket. + NOTSUP # Not supported, or operation not supported on socket. + NOTTY # Inappropriate I/O control operation. + NXIO # No such device or address. + OVERFLOW # Value too large to be stored in data type. + OWNERDEAD # Previous owner died. + PERM # Operation not permitted. + PIPE # Broken pipe. + PROTO # Protocol error. + PROTONOSUPPORT # Protocol not supported. + PROTOTYPE # Protocol wrong type for socket. + RANGE # Result too large. + ROFS # Read-only file system. + SPIPE # Invalid seek. + SRCH # No such process. + STALE # Reserved. + TIMEDOUT # Connection timed out. + TXTBSY # Text file busy. + XDEV # Cross-device link. + NOTCAPABLE # Extension: Capabilities insufficient. +end