3402ca346f8818dea80e06b735748081327a0fe3 — Marek Marecki 2 months ago 8d1f7ec + 9732974
Merge branch 'master' into devel
53 files changed, 2291 insertions(+), 162 deletions(-)

M Changelog.markdown
M Makefile
M README.asm
M README.markdown
A build/scheduler/io/.gitkeep
A docs/design/new_io.markdown
M include/viua/assembler/backend/op_assemblers.h
M include/viua/assembler/frontend/static_analyser.h
M include/viua/bytecode/maps.h
M include/viua/bytecode/opcodes.h
M include/viua/bytecode/operand_types.h
M include/viua/cg/bytecode/instructions.h
M include/viua/kernel/kernel.h
M include/viua/process.h
M include/viua/program.h
A include/viua/scheduler/io.h
A include/viua/scheduler/io/interactions.h
M include/viua/scheduler/process.h
A include/viua/types/io.h
M include/viua/version.h
A io.asm
A net_server.asm
A net_server_run.sh
M sample/asm/exceptions/terminating_processes.asm
A src/assembler/backend/op_assemblers/assemble_op_io_wait.cpp
A src/assembler/frontend/static_analyser/checkers/check_op_io_cancel.cpp
A src/assembler/frontend/static_analyser/checkers/check_op_io_close.cpp
A src/assembler/frontend/static_analyser/checkers/check_op_io_read.cpp
A src/assembler/frontend/static_analyser/checkers/check_op_io_wait.cpp
A src/assembler/frontend/static_analyser/checkers/check_op_io_write.cpp
M src/assembler/frontend/static_analyser/checkers/utils.cpp
M src/assembler/frontend/static_analyser/register_usage.cpp
M src/cg/bytecode/instructions.cpp
M src/cg/disassembler/disassembler.cpp
M src/cg/tools.cpp
M src/front/asm/assemble_instruction.cpp
M src/front/kernel.cpp
M src/kernel/kernel.cpp
M src/kernel/registerset.cpp
M src/process.cpp
M src/process/dispatch.cpp
A src/process/instr/io.cpp
M src/programinstructions.cpp
A src/scheduler/io/request.cpp
A src/scheduler/io/scheduler.cpp
M src/scheduler/process.cpp
A src/stdlib/posix/io.cpp
M src/stdlib/posix/network.cpp
M src/tooling/libs/lexer/normaliser.cpp
M src/tooling/libs/parser/parse.cpp
M src/tooling/libs/static_analyser/static_analyser.cpp
A src/types/io.cpp
M tests/tests.py
M Changelog.markdown => Changelog.markdown +11 -0
@@ 109,6 109,9 @@ There are several categories of change:
   bits and integers
 - fix: target of `send` instruction may be given by pointer dereference
 - bic: rename `VIUA_VP_SCHEDULERS` to `VIUA_PROC_SCHEDULERS`
+- feature: I/O instructions (`io_read`, `io_write`, `io_wait`, `io_cancel`) to interact
+  with the outside world using I/O ports
+- feature: `VIUA_IO_SCHEDULERS` to limit the number of I/O schedulers spawned
 
 Fixed-width arithmetic instructions interpret bit strings as two's complement
 fixed-width integers when signed arithmetic is requested.


@@ 118,6 121,14 @@ impossible for a process to become stuck in a "free process" queue... because
 there is no such queue! The new algorithm is a very primitive implementation of
 work-stealing so every process is always owned by a scheduler.
 
+However, the "free X" queues are not gone from VM's kernel. In fact, while the
+"free process" queue might have been removed, a new "free I/O request" queue was
+introduced. Along with a completely new I/O subsystem, I/O instructions and a
+major revamp in the way I/O is performed by programs running on Viua VM.
+
+A dedicated I/O subsystem will free the generic FFI subsystem to do more work
+not related to I/O that has to be done outside of the "normal" VM constraints.
+
 ----
 
 # From 0.8.4 to 0.9.0

M Makefile => Makefile +15 -1
@@ 343,6 343,7 @@ platform: build/platform/types/exception.o \
 	build/platform/types/pointer.o \
 	build/platform/types/number.o \
 	build/platform/types/integer.o \
+	build/platform/types/io.o \
 	build/platform/types/bits.o \
 	build/platform/types/float.o \
 	build/platform/types/string.o \


@@ 479,8 480,9 @@ build/cg/%.o: src/cg/%.cpp
 
 ############################################################
 # VIRTUAL MACHINE CODE
-VIUA_INSTR_FILES_O=build/process/instr/atom.o \
+VIUA_INSTR_FILES_O=\
 				   build/process/instr/arithmetic.o \
+				   build/process/instr/atom.o \
 				   build/process/instr/bits.o \
 				   build/process/instr/bool.o \
 				   build/process/instr/calls.o \


@@ 490,6 492,7 @@ VIUA_INSTR_FILES_O=build/process/instr/atom.o \
 				   build/process/instr/float.o \
 				   build/process/instr/general.o \
 				   build/process/instr/int.o \
+				   build/process/instr/io.o \
 				   build/process/instr/linking.o \
 				   build/process/instr/registers.o \
 				   build/process/instr/str.o \


@@ 506,6 509,7 @@ VIUA_TYPES_FILES_O=build/types/atom.o \
 				   build/types/float.o \
 				   build/types/function.o \
 				   build/types/integer.o \
+				   build/types/io.o \
 				   build/types/number.o \
 				   build/types/object.o \
 				   build/types/pointer.o \


@@ 529,6 533,8 @@ build/bin/vm/kernel: build/front/kernel.o \
 	build/process/dispatch.o \
 	build/scheduler/ffi/request.o \
 	build/scheduler/ffi/scheduler.o \
+	build/scheduler/io/request.o \
+	build/scheduler/io/scheduler.o \
 	build/kernel/registerset.o \
 	build/kernel/frame.o \
 	build/loader.o \


@@ 554,6 560,7 @@ OP_ASSEMBLERS= \
 	build/assembler/backend/op_assemblers/assemble_op_frame.o \
 	build/assembler/backend/op_assemblers/assemble_op_if.o \
 	build/assembler/backend/op_assemblers/assemble_op_integer.o \
+	build/assembler/backend/op_assemblers/assemble_op_io_wait.o \
 	build/assembler/backend/op_assemblers/assemble_op_join.o \
 	build/assembler/backend/op_assemblers/assemble_op_jump.o \
 	build/assembler/backend/op_assemblers/assemble_op_process.o \


@@ 632,6 639,11 @@ build/bin/vm/asm: build/front/asm.o \
 	build/assembler/frontend/static_analyser/checkers/check_op_iinc.o \
 	build/assembler/frontend/static_analyser/checkers/check_op_integer.o \
 	build/assembler/frontend/static_analyser/checkers/check_op_integer_of_bits.o \
+	build/assembler/frontend/static_analyser/checkers/check_op_io_cancel.o \
+	build/assembler/frontend/static_analyser/checkers/check_op_io_close.o \
+	build/assembler/frontend/static_analyser/checkers/check_op_io_read.o \
+	build/assembler/frontend/static_analyser/checkers/check_op_io_wait.o \
+	build/assembler/frontend/static_analyser/checkers/check_op_io_write.o \
 	build/assembler/frontend/static_analyser/checkers/check_op_isnull.o \
 	build/assembler/frontend/static_analyser/checkers/check_op_itof.o \
 	build/assembler/frontend/static_analyser/checkers/check_op_izero.o \


@@ 761,6 773,7 @@ stdlib: build/bin/vm/asm \
 	build/stdlib/Std/Io.so \
 	build/stdlib/std/posix/network.so \
 	build/stdlib/Std/Posix/Network.so \
+	build/stdlib/std/posix/io.so \
 	build/stdlib/Std/Random.so \
 	build/stdlib/std/kitchensink.so
 


@@ 794,6 807,7 @@ build/stdlib/std/os.so: build/stdlib/std/os.o
 build/stdlib/std/io.so: build/stdlib/std/io.o
 
 build/stdlib/std/posix/network.so: build/stdlib/std/posix/network.o
+build/stdlib/std/posix/io.so: build/stdlib/std/posix/io.o
 
 build/stdlib/std/random.so: build/stdlib/std/random.o
 

M README.asm => README.asm +26 -17
@@ 4,30 4,39 @@
 ;
 .function: main/0
     ; Allocate local register set.
-    ; This is done explicitly by the callee because it is an
-    ; implementation detail - if a newer version of the function
-    ; requires fewer registers the code using it should not have
-    ; to be compiled.
-    ; Also, when a module is reloaded the code using it can stay
-    ; the same as long as the number and types of parameters of
-    ; the function stay the same.
-    ;
-    ; In this function, two local registers are allocated.
-    allocate_registers %2 local
+    ; This is done explicitly by the callee because it is an implementation
+    ; detail - if a newer version of the function requires fewer (or more)
+    ; registers the code using it should not have to be recompiled.
+    ; Also, when a module is reloaded the code using it can stay the same as
+    ; long as the number and types of parameters of the function stay the same.
+    allocate_registers %4 local
 
-    ; Store text value in register with index 1.
+    ; Store text value in local register with index 1.
     ; The text instruction shares operand order with majority of
     ; other instructions:
     ;
     ;   <mnemonic> <target> <sources>...
     ;
-    text %1 local "Hello World!"
+    text %1 local "Hello World!\n"
+
+    ; Store integer value in local register 2.
+    ; Use a 1 because it will be used to represent the standard output stream on
+    ; UNIX-like systems.
+    integer %2 local 1
+
+    ; Write "Hello World!" on standard output. The io_write instruction will
+    ; create an I/O request that will be carried out asynchronously and
+    ; immediately return control to the process.
+    io_write %3 local %2 local %1 local
+
+    io_close %2 local
 
-    ; Print contents of a register to standard output.
-    ; This is the most primitive form of output the VM supports, and
-    ; should be used only in the simplest programs and
-    ; for quick-and-dirty debugging.
-    print %1 local
+    ; Wait for the completion of the I/O request. In this case, the timeout
+    ; given is 1 second.
+    ; Void register (signified by the use of 'void') is used to tell the
+    ; instruction that the result of the I/O operation is not needed and may be
+    ; discarded.
+    io_wait void %3 local 1s
 
     ; The finishing sequence of every program running on Viua.
     ;

M README.markdown => README.markdown +10 -35
@@ 28,47 28,22 @@ by issue `7c06177872c3a718510a54e6513820f8fe0fb99b` in the embedded issue reposi
 #### Hello World in Viua VM
 
 ```
-; Main function of the program, automatically invoked by the VM.
-; It must be present in every executable file.
-; Valid main functions are main/0, main/1 and main/2.
-;
 .function: main/0
-    ; Allocate local register set.
-    ; This is done explicitly by the callee because it is an
-    ; implementation detail - if a newer version of the function
-    ; requires fewer registers the code using it should not have
-    ; to be compiled.
-    ; Also, when a module is reloaded the code using it can stay
-    ; the same as long as the number and types of parameters of
-    ; the function stay the same.
-    ;
-    ; In this function, two local registers are allocated.
-    allocate_registers %2 local
-
-    ; Store text value in register with index 1.
-    ; The text instruction shares operand order with majority of
-    ; other instructions:
-    ;
-    ;   <mnemonic> <target> <sources>...
-    ;
-    text %1 local "Hello World!"
-
-    ; Print contents of a register to standard output.
-    ; This is the most primitive form of output the VM supports, and
-    ; should be used only in the simplest programs and
-    ; for quick-and-dirty debugging.
-    print %1 local
-
-    ; The finishing sequence of every program running on Viua.
-    ;
-    ; Register 0 is used to store return value of a function, and
-    ; the assembler will enforce that it is set to integer 0 for main
-    ; function.
+    allocate_registers %3 local
+
+    text %1 local "Hello World!\n"
+    integer %2 local 1
+
+    io_write %2 local %2 local %1 local
+    io_wait void %2 local 1s
+
     izero %0 local
     return
 .end
 ```
 
+See `README.asm` file to see commented version of this sample program.
+
 For more examples visit either [documentation](http://docs.viuavm.org/) or [Rosetta](http://rosettacode.org/wiki/Viua_VM_assembly) page for Viua VM.
 Check [Weekly](http://weekly.viuavm.org/) blog for news and developments in Viua VM.
 

A build/scheduler/io/.gitkeep => build/scheduler/io/.gitkeep +0 -0

A docs/design/new_io.markdown => docs/design/new_io.markdown +128 -0
@@ 0,0 1,128 @@
+# POSIX systems call implementing I/O 
+
+Obtaining and closing file descriptors:
+
+- open(2)
+- close(2)
+
+I/O operations on files:
+
+- write(2)
+- writev(2)
+- read(2)
+- readv(2)
+
+I/O operations on sockets:
+
+- send(2)
+- sendto(2)
+- sendmsg(2)
+- recv(2)
+- recvfrom(2)
+- recvmsg(2)
+
+Basic polling:
+
+- poll(2)
+- select(2)
+
+Sockets:
+
+- socket(2), socket(7)
+- connect(2)
+- bind(2)
+- listen(2)
+- accept(2)
+- socketpair(2)
+- getsockname(2)
+- getpeername(2)
+- getsockopt(2), setsockopt(2)
+- shutdown(2)
+
+Nonblocking I/O:
+
+- fcntl(2)
+
+Man pages:
+
+- socket(7)
+- aio(7)
+
+--------------------------------------------------------------------------------
+
+# I/O handles
+
+Handle acts as a generic object that interacts in the outside world in any way.
+
+- file
+- socket
+- pipe
+- directory
+
+--------------------------------------------------------------------------------
+
+# I/O ports
+
+An I/O port is something which can be used to read bytes from, and to write
+bytes to. Some ports may also support seeking.
+
+- file
+- socket
+- pipe
+
+The abstraction is very simple: the user program interacts with the port, and
+the port interacts with the outside world (acts as a sort of proxy between VM
+world and the outside world). This allows specially-crafted ports to use
+something else than plain byte vectors to communicate with the user program
+running on Viua VM; for example, a port may perform serialisation and
+deserialisation of data.
+
+A port must specify that it is not a "dumb pipe" if it wants to perform
+additional processing on data that passes through it. Also, the processing is
+scheduled on a different scheduler than the regular I/O scheduler to avoid
+accidental starvation of "dumb pipes" by a misbehaving port.
+
+A pipeline for writing could look like this:
+
+- user program opens a port
+- user program writes a data structure to the port...
+- the port serialises the data structure to an on-the-wire format
+- the port writes the serialised form to the file descriptor
+- ...user program continues
+
+A pipeline for reading could look like this:
+
+- user program opens a port
+- user program reads a data structure from the port...
+- the port reads some bytes from the file descriptor
+- the port deserialises the data from on-the-wire format to a data structure
+- ...user program continues
+
+If the port is just a "dumb pipe" then the serialisation/deserialisation step is
+removed and it's just bytes that go through that port.
+
+The question now is: should 
+
+--------------------------------------------------------------------------------
+
+# I/O operations
+
+Not all ports and handles must support all operations. Some ports may be
+read-only (e.g. a source of random bytes) or write-only (e.g. the standard
+output stream).
+
+- read (recv): reads bytes from a port
+- write (send): writes bytes to a port
+- seek: sets the stream pointer position
+- tell: reports the stream pointer position
+- op: operation determined by the opcode, used (opcode, struct) pair to tell the
+  port what needs to be done
+
+--------------------------------------------------------------------------------
+
+# First things first
+
+Allow opening, reading from, and writing to files. That's a priority. Then do
+the same but for sockets.
+
+Only then think about how to include opening stuff like directories in this.

M include/viua/assembler/backend/op_assemblers.h => include/viua/assembler/backend/op_assemblers.h +2 -0
@@ 291,6 291,8 @@ auto assemble_op_receive(Program&, std::vector<Token> const&, Token_index const)
     -> void;
 auto assemble_op_attach(Program&, std::vector<Token> const&, Token_index const)
     -> void;
+auto assemble_op_io_wait(Program&, std::vector<Token> const&, Token_index const)
+    -> void;
 }}}}  // namespace viua::assembler::backend::op_assemblers
 
 

M include/viua/assembler/frontend/static_analyser.h => include/viua/assembler/frontend/static_analyser.h +10 -0
@@ 498,6 498,16 @@ auto check_op_structat(Register_usage_profile& register_usage_profile,
                        Instruction const& instruction) -> void;
 auto check_op_structkeys(Register_usage_profile& register_usage_profile,
                          Instruction const& instruction) -> void;
+auto check_op_io_read(Register_usage_profile& register_usage_profile,
+                         Instruction const& instruction) -> void;
+auto check_op_io_write(Register_usage_profile& register_usage_profile,
+                         Instruction const& instruction) -> void;
+auto check_op_io_wait(Register_usage_profile& register_usage_profile,
+                         Instruction const& instruction) -> void;
+auto check_op_io_cancel(Register_usage_profile& register_usage_profile,
+                         Instruction const& instruction) -> void;
+auto check_op_io_close(Register_usage_profile& register_usage_profile,
+                         Instruction const& instruction) -> void;
 
 auto check_for_unused_registers(
     Register_usage_profile const& register_usage_profile) -> void;

M include/viua/bytecode/maps.h => include/viua/bytecode/maps.h +6 -0
@@ 200,6 200,12 @@ std::map<enum OPCODE, std::string> const OP_NAMES = {
     {STRUCTAT, "structat"},
     {STRUCTKEYS, "structkeys"},
 
+    {IO_READ, "io_read"},
+    {IO_WRITE, "io_write"},
+    {IO_CLOSE, "io_close"},
+    {IO_WAIT, "io_wait"},
+    {IO_CANCEL, "io_cancel"},
+
     {RETURN, "return"},
     {HALT, "halt"},
 };

M include/viua/bytecode/opcodes.h => include/viua/bytecode/opcodes.h +6 -0
@@ 436,6 436,12 @@ enum OPCODE : viua::internals::types::byte {
      */
     STRUCTKEYS,
 
+    IO_READ,
+    IO_WRITE,
+    IO_CLOSE,
+    IO_WAIT,
+    IO_CANCEL,
+
     RETURN,
     HALT,
 };

M include/viua/bytecode/operand_types.h => include/viua/bytecode/operand_types.h +4 -0
@@ 134,6 134,10 @@ enum class Value_types : ValueTypesType {
     OBJECT = 1 << 14,
 
     POINTER = 1 << 15,
+
+    IO_REQUEST = 1 << 16,
+    IO_PORT = 1 << 17,
+    IO_PORT_LIKE = (IO_PORT | INTEGER),
 };
 }}  // namespace viua::internals
 

M include/viua/cg/bytecode/instructions.h => include/viua/cg/bytecode/instructions.h +11 -0
@@ 378,6 378,17 @@ auto opinsert(viua::internals::types::byte*, int_op, int_op, int_op)
 auto opremove(viua::internals::types::byte*, int_op, int_op, int_op)
     -> viua::internals::types::byte*;
 
+auto op_io_read(viua::internals::types::byte*, int_op, int_op, int_op)
+    -> viua::internals::types::byte*;
+auto op_io_write(viua::internals::types::byte*, int_op, int_op, int_op)
+    -> viua::internals::types::byte*;
+auto op_io_close(viua::internals::types::byte*, int_op, int_op)
+    -> viua::internals::types::byte*;
+auto op_io_wait(viua::internals::types::byte*, int_op, int_op, timeout_op)
+    -> viua::internals::types::byte*;
+auto op_io_cancel(viua::internals::types::byte*, int_op)
+    -> viua::internals::types::byte*;
+
 auto opreturn(viua::internals::types::byte*) -> viua::internals::types::byte*;
 auto ophalt(viua::internals::types::byte*) -> viua::internals::types::byte*;
 }}  // namespace cg::bytecode

M include/viua/kernel/kernel.h => include/viua/kernel/kernel.h +58 -1
@@ 22,11 22,12 @@
 
 #pragma once
 
+#include <dlfcn.h>
 #include <algorithm>
 #include <atomic>
 #include <condition_variable>
 #include <cstdint>
-#include <dlfcn.h>
+#include <deque>
 #include <iostream>
 #include <map>
 #include <memory>


@@ 55,6 56,11 @@ class Process_scheduler;
 namespace ffi {
 class Foreign_function_call_request;
 }
+
+namespace io {
+class IO_request;
+struct IO_interaction;
+}
 }  // namespace scheduler
 }  // namespace viua
 


@@ 202,6 208,49 @@ class Kernel {
     std::vector<std::unique_ptr<std::thread>> foreign_call_workers;
 
     /*
+     * I/O SCHEDULING
+     */
+    std::deque<std::unique_ptr<viua::scheduler::io::IO_interaction>>
+        io_request_queue;
+    std::mutex io_request_mutex;
+    std::condition_variable io_request_cv;
+    std::vector<std::unique_ptr<std::thread>> io_workers;
+
+  public:
+    class IO_result {
+        IO_result(bool const, std::unique_ptr<viua::types::Value>);
+      public:
+        std::unique_ptr<viua::types::Value> value;
+        std::unique_ptr<viua::types::Value> error;
+
+        bool is_complete;
+        bool is_cancelled;
+        bool is_successful;
+
+        IO_result() = default;
+        IO_result(IO_result const&) = delete;
+        auto operator=(IO_result const&) -> IO_result& = delete;
+        IO_result(IO_result&& that)
+            : value{std::move(that.value)}
+            , error{std::move(that.error)}
+            , is_complete{that.is_complete}
+            , is_cancelled{that.is_cancelled}
+            , is_successful{that.is_successful}
+        {}
+        auto operator=(IO_result&& that) -> IO_result& = delete;
+
+        static auto make_success(std::unique_ptr<viua::types::Value>) -> IO_result;
+        static auto make_error(std::unique_ptr<viua::types::Value>) -> IO_result;
+    };
+
+  private:
+    // FIXME Use viua::scheduler::io::IO_interaction::id_type.
+    mutable std::mutex io_requests_mtx;
+    std::map<std::tuple<uint64_t, uint64_t>, viua::scheduler::io::IO_interaction*> io_requests;
+    mutable std::mutex io_result_mtx;
+    std::map<std::tuple<uint64_t, uint64_t>, IO_result> io_results;
+
+    /*
      * MESSAGE PASSING
      *
      * Why are mailboxes are kept inside the kernel? To remove the need to look


@@ 297,10 346,18 @@ class Kernel {
                  std::queue<std::unique_ptr<viua::types::Value>>&);
     uint64_t pids() const;
 
+    auto schedule_io(std::unique_ptr<viua::scheduler::io::IO_interaction>) -> void;
+    auto cancel_io(std::tuple<uint64_t, uint64_t> const) -> void;
+    auto complete_io(std::tuple<uint64_t, uint64_t> const, IO_result) -> void;
+    auto io_complete(std::tuple<uint64_t, uint64_t> const) const -> bool;
+    auto io_result(std::tuple<uint64_t, uint64_t> const) -> std::unique_ptr<viua::types::Value>;
+
     auto static no_of_process_schedulers()
         -> viua::internals::types::schedulers_count;
     auto static no_of_ffi_schedulers()
         -> viua::internals::types::schedulers_count;
+    auto static no_of_io_schedulers()
+        -> viua::internals::types::schedulers_count;
     auto static is_tracing_enabled() -> bool;
 
     int run();

M include/viua/process.h => include/viua/process.h +20 -2
@@ 34,6 34,7 @@
 #include <viua/kernel/frame.h>
 #include <viua/kernel/registerset.h>
 #include <viua/kernel/tryframe.h>
+#include <viua/scheduler/io/interactions.h>
 #include <viua/pid.h>
 #include <viua/types/value.h>
 


@@ 44,6 45,11 @@ class Halt_exception : public std::runtime_error {
 };
 
 
+namespace viua::types {
+    struct IO_interaction;
+}
+
+
 namespace viua { namespace scheduler {
 class Virtual_process_scheduler;
 class Process_scheduler;


@@ 296,8 302,11 @@ class Process {
     viua::process::PID process_id;
     bool is_hidden;
 
-    /*  Timeouts for message passing, and
-     *  multiprocessing.
+    /*
+     * Timeouts for receiving messages, waiting for processes, and waiting for
+     * I/O. They can be reused for all these things since for the duration of
+     * each type of wait the process is suspended and is unable to execute any
+     * code.
      */
     std::chrono::steady_clock::time_point waiting_until;
     bool timeout_active      = false;


@@ 459,6 468,12 @@ class Process {
 
     auto opimport(Op_address_type) -> Op_address_type;
 
+    auto op_io_read(Op_address_type) -> Op_address_type;
+    auto op_io_write(Op_address_type) -> Op_address_type;
+    auto op_io_close(Op_address_type) -> Op_address_type;
+    auto op_io_wait(Op_address_type) -> Op_address_type;
+    auto op_io_cancel(Op_address_type) -> Op_address_type;
+
   public:
     auto dispatch(Op_address_type) -> Op_address_type;
     auto tick() -> Op_address_type;


@@ 518,6 533,9 @@ class Process {
         return is_pinned_to_scheduler;
     }
 
+    auto schedule_io(std::unique_ptr<viua::scheduler::io::IO_interaction>) -> void;
+    auto cancel_io(std::tuple<uint64_t, uint64_t> const) -> void;
+
     Process(std::unique_ptr<Frame>,
             viua::scheduler::Process_scheduler*,
             viua::process::Process*,

M include/viua/program.h => include/viua/program.h +6 -0
@@ 226,6 226,12 @@ class Program {
     auto opstructat(int_op const, int_op const, int_op const) -> Program&;
     auto opstructkeys(int_op const, int_op const) -> Program&;
 
+    auto op_io_read(int_op const, int_op const, int_op const) -> Program&;
+    auto op_io_write(int_op const, int_op const, int_op const) -> Program&;
+    auto op_io_close(int_op const, int_op const) -> Program&;
+    auto op_io_wait(int_op const, int_op const, timeout_op) -> Program&;
+    auto op_io_cancel(int_op const) -> Program&;
+
     auto opreturn() -> Program&;
     auto ophalt() -> Program&;
 

A include/viua/scheduler/io.h => include/viua/scheduler/io.h +50 -0
@@ 0,0 1,50 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef VIUA_SCHEDULER_IO_H
+#define VIUA_SCHEDULER_IO_H
+
+#include <condition_variable>
+#include <deque>
+#include <memory>
+#include <mutex>
+#include <viua/scheduler/io/interactions.h>
+
+
+namespace viua {
+namespace process {
+class Process;
+}
+namespace kernel {
+class Kernel;
+}
+}  // namespace viua
+
+
+namespace viua { namespace scheduler { namespace io {
+void io_scheduler(
+    uint64_t const,
+    viua::kernel::Kernel&,
+    std::deque<std::unique_ptr<viua::scheduler::io::IO_interaction>>& requests,
+    std::mutex& mtx,
+    std::condition_variable& cv);
+}}}  // namespace viua::scheduler::io
+
+
+#endif

A include/viua/scheduler/io/interactions.h => include/viua/scheduler/io/interactions.h +147 -0
@@ 0,0 1,147 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef VIUA_SCHEDULER_IO_INTERACTIONS_H
+#define VIUA_SCHEDULER_IO_INTERACTIONS_H
+
+#include <atomic>
+#include <optional>
+
+namespace viua { namespace scheduler { namespace io {
+enum class IO_kind : uint8_t {
+    Input,
+    Output,
+};
+
+struct IO_interaction {
+    using id_type = std::tuple<uint64_t, uint64_t>;
+    using fd_type = int;
+
+    enum class State : uint8_t {
+        Queued,
+        In_flight,
+        Complete,
+    };
+    enum class Status : uint8_t {
+        Success,
+        Error,
+        Cancelled,
+    };
+
+  private:
+    id_type const assigned_id;
+
+    std::atomic_bool is_complete = false;
+    std::atomic_bool is_cancelled = false;
+    std::atomic_bool is_aborted = false;
+
+  public:
+    /*
+     * This function should try to perform the I/O interaction
+     * represented by this object.
+     *
+     * RETURN VALUE
+     *
+     * If the function returns a non-null pointer it is the return value
+     * of the interaction and the interaction is finished; otherwise,
+     * the interaction is not finished and must the scheduler must try
+     * to run it again.
+     *
+     * The interaction is responsible for any buffering necessary to
+     * produce a return value. For example, if the port that produced
+     * the interaction returns 1000-byte blocks of data and the
+     * interaction only managed to read 800 bytes it must buffer them
+     * and not return until manages to read 200 bytes more.
+     *
+     * BLOCKING
+     *
+     * The interact function MUST NOT block.
+     *
+     * CANCELLING
+     *
+     * Interactions react differently to being cancelled. Some may
+     * return partial results, some just flat out crash and return an
+     * exception.
+     *
+     * POSTPROCESSING
+     *
+     * Keep any postprocessing to the absolute minimum. Returning raw
+     * data and offering a function to process it outside of I/O
+     * scheduler is a much better solution. This means, though, that
+     * sometimes the I/O may be split into several functions. Spawn a
+     * special-purpose actor if it too cumbersome to manage in-line.
+     */
+    struct Interaction_result {
+        using result_type = std::unique_ptr<viua::types::Value>;
+
+        State const state = State::In_flight;
+        std::optional<Status> const status;
+        std::unique_ptr<viua::types::Value> result;
+
+        Interaction_result() = default;
+        Interaction_result(State const, Status const, result_type);
+    };
+    virtual auto interact() -> Interaction_result = 0;
+
+    auto id() const -> id_type;
+    virtual auto fd() const -> std::optional<fd_type> { return std::nullopt; }
+    virtual auto kind() const -> IO_kind = 0;
+
+    auto cancel() -> void;
+    auto cancelled() const -> bool;
+
+    auto abort() -> void;
+    auto aborted() const -> bool;
+
+    auto complete() -> void;
+    auto completed() const -> bool;
+
+    IO_interaction(id_type const);
+    virtual ~IO_interaction();
+};
+struct IO_fake_interaction : public IO_interaction {
+    auto interact() -> Interaction_result override;
+
+    using IO_interaction::IO_interaction;
+};
+struct IO_read_interaction : public IO_interaction {
+    int const file_descriptor;
+    std::string buffer;
+
+    auto interact() -> Interaction_result override;
+
+    auto fd() const -> std::optional<fd_type> { return file_descriptor; }
+    auto kind() const -> IO_kind { return IO_kind::Input; }
+
+    IO_read_interaction(id_type const, int const, size_t const);
+};
+struct IO_write_interaction : public IO_interaction {
+    int const file_descriptor;
+    std::string const buffer;
+
+    auto interact() -> Interaction_result override;
+
+    auto fd() const -> std::optional<fd_type> { return file_descriptor; }
+    auto kind() const -> IO_kind { return IO_kind::Output; }
+
+    IO_write_interaction(id_type const, int const, std::string);
+};
+}}}
+
+#endif

M include/viua/scheduler/process.h => include/viua/scheduler/process.h +17 -0
@@ 20,8 20,16 @@
 #ifndef VIUA_SCHEDULER_PROCESS_H
 #define VIUA_SCHEDULER_PROCESS_H
 
+#include <atomic>
 #include <deque>
 #include <mutex>
+#include <optional>
+#include <queue>
+#include <thread>
+#include <vector>
+#include <viua/kernel/frame.h>
+#include <viua/scheduler/io/interactions.h>
+#include <viua/pid.h>
 
 namespace viua {
     namespace process {


@@ 183,6 191,15 @@ class Process_scheduler {
     auto shutdown() -> void;
     auto join() -> void;
     auto exit() const -> int;
+
+    auto kernel() const -> viua::kernel::Kernel& {
+        return attached_kernel;
+    }
+
+    auto schedule_io(std::unique_ptr<viua::scheduler::io::IO_interaction>) -> void;
+    auto cancel_io(std::tuple<uint64_t, uint64_t> const) -> void;
+    auto io_complete(std::tuple<uint64_t, uint64_t> const) const -> bool;
+    auto io_result(std::tuple<uint64_t, uint64_t> const) -> std::unique_ptr<viua::types::Value>;
 };
 }}
 

A include/viua/types/io.h => include/viua/types/io.h +123 -0
@@ 0,0 1,123 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef VIUA_TYPES_IO_H
+#define VIUA_TYPES_IO_H
+
+#include <string>
+#include <viua/kernel/frame.h>
+#include <viua/kernel/registerset.h>
+#include <viua/scheduler/io/interactions.h>
+#include <viua/support/string.h>
+#include <viua/types/integer.h>
+#include <viua/types/value.h>
+#include <viua/types/vector.h>
+#include <viua/process.h>
+
+
+namespace viua { namespace types {
+class IO_request : public Value {
+    using interaction_id_type = viua::scheduler::io::IO_interaction::id_type;
+    interaction_id_type const interaction_id;
+    viua::kernel::Kernel* const kernel;
+  public:
+    static std::string const type_name;
+
+    std::string type() const override;
+    std::string str() const override;
+    std::string repr() const override;
+    bool boolean() const override;
+
+    auto id() const -> interaction_id_type {
+        return interaction_id;
+    }
+
+    std::unique_ptr<Value> copy() const override;
+
+    IO_request(viua::kernel::Kernel*, interaction_id_type const);
+    ~IO_request();
+};
+
+class IO_port : public Value {
+  public:
+      using counter_type = uint64_t;
+  protected:
+      counter_type counter = 0;
+  public:
+    static std::string const type_name;
+
+    std::string type() const override;
+    std::string str() const override;
+    std::string repr() const override;
+    bool boolean() const override;
+
+    std::unique_ptr<Value> copy() const override;
+
+    virtual auto read(
+          viua::kernel::Kernel&
+        , std::unique_ptr<Value>
+    ) -> std::unique_ptr<IO_request> = 0;
+    virtual auto write(
+          viua::kernel::Kernel&
+        , std::unique_ptr<Value>
+    ) -> std::unique_ptr<IO_request> = 0;
+
+    virtual auto close() -> void = 0;
+
+    IO_port();
+    ~IO_port();
+};
+
+class IO_fd : public IO_port {
+  public:
+    enum class Ownership : bool {
+        Owned,
+        Borrowed,
+    };
+
+  private:
+    /*
+     * The simples possible I/O port represents the "file descriptor" known from
+     * the usual POSIX interfaces. It supports two basic operations: reading and
+     * writing.
+     */
+    int const file_descriptor;
+    Ownership const ownership;
+  public:
+    static std::string const type_name;
+
+    std::string type() const override;
+    std::string str() const override;
+    std::string repr() const override;
+    bool boolean() const override;
+
+    std::unique_ptr<Value> copy() const override;
+
+    auto fd() const -> int;
+    auto read(viua::kernel::Kernel&, std::unique_ptr<Value>) -> std::unique_ptr<IO_request> override;
+    auto write(viua::kernel::Kernel&, std::unique_ptr<Value>) -> std::unique_ptr<IO_request> override;
+    auto close() -> void override;
+
+    IO_fd(int const, Ownership const = Ownership::Owned);
+    ~IO_fd();
+};
+}}  // namespace viua::types
+
+
+#endif

M include/viua/version.h => include/viua/version.h +1 -1
@@ 36,6 36,6 @@
 #pragma once
 
 const char* VERSION = "0.9.0";
-const char* MICRO   = "2510";
+const char* MICRO   = "2545";
 
 #endif

A io.asm => io.asm +56 -0
@@ 0,0 1,56 @@
+.function: yep/2
+    allocate_registers %6 local
+
+    .name: 5 parent
+    move %parent local %0 parameters
+
+    .name: 1 fd_in
+    .name: 3 stdout
+    move %fd_in local %1 parameters
+    integer %stdout local 1
+
+    .name: 2 buffer_size
+    integer %buffer_size local 512
+
+    .name: 4 data
+    io_read %data local %fd_in local %buffer_size local
+    print %data local
+
+    ;io_cancel %4 local
+    io_wait %data local %data local 10s
+
+    ;print %4 local
+    io_write %data local %stdout local %data local
+    ;io_wait %data local %data local 1s
+
+    send %parent local %data local
+
+    return
+.end
+
+.import: [[dynamic]] std::posix::io
+.signature: std::posix::io::open/1
+
+.function: main/0
+    allocate_registers %2 local
+
+    frame %1
+    text %1 local "./io.asm"
+    move %0 arguments %1 local
+    call %1 local std::posix::io::open/1
+
+    frame %2
+    ;integer %1 local 0
+    move %1 arguments %1 local
+    self %1 local
+    move %0 arguments %1 local
+    process void yep/2
+
+    receive %0 local 10s
+    print %0 local
+    io_wait %0 local %0 local 1s
+    print %0 local
+
+    izero %0 local
+    return
+.end

A net_server.asm => net_server.asm +78 -0
@@ 0,0 1,78 @@
+.import: [[dynamic]] std::posix::network
+.signature: std::posix::network::socket/0
+.signature: std::posix::network::bind/3
+.signature: std::posix::network::listen/2
+.signature: std::posix::network::accept/1
+
+.function: [[no_sa]] reader_of_messages/1
+    allocate_registers %4 local
+
+    .name: iota client_sock
+    move %client_sock local %0 parameters
+
+    .name: iota buffer_size
+    .name: iota req
+    integer %buffer_size 1024
+    io_read %req local %client_sock local %buffer_size local
+    print %req local
+
+    io_wait %0 local %req local 10s
+    print %0 local
+
+    return
+.end
+
+.function: main/1
+    allocate_registers %7 local
+
+    .name: iota sock
+    .name: iota server_addr
+    .name: iota server_port
+    .name: iota tmp
+
+    ; 1/ Create the socket.
+    frame %0
+    call %sock local std::posix::network::socket/0
+    print %sock local
+
+    ; 2/ Store the address.
+    string %server_addr local "127.0.0.1"
+    print %server_addr local
+
+    ; 3/ Store the port.
+    move %tmp local %0 parameters
+    integer %server_port local 1
+    vat %server_port local %tmp local %server_port local
+    stoi %server_port local *server_port local
+    print %server_port local
+
+    ; 4/ Bind the socket and mark it as listening for connections.
+    frame %3
+    move %0 arguments %sock local
+    move %1 arguments %server_addr local
+    move %2 arguments %server_port local
+    call %sock local std::posix::network::bind/3
+
+    .name: iota backlog
+    integer %backlog local 1
+    frame %2
+    move %0 arguments %sock local
+    move %1 arguments %backlog local
+    call %sock local std::posix::network::listen/2
+
+    ; 5/ Accept a connection and read a message.
+    .name: iota client_sock
+    frame %1
+    ptr %tmp local %sock local
+    print %tmp local
+    move %0 arguments %tmp local
+    call %client_sock local std::posix::network::accept/1
+    print %client_sock local
+
+    frame %1
+    move %0 arguments %client_sock local
+    process void reader_of_messages/1
+
+    izero %0 local
+    return
+.end

A net_server_run.sh => net_server_run.sh +12 -0
@@ 0,0 1,12 @@
+#!/usr/bin/env bash
+
+set -e
+
+export VIUA_LIBRARY_PATH=./build/stdlib
+export VIUA_PROC_SCHEDULERS=4
+export VIUA_IO_SCHEDULERS=1
+export VIUA_FFI_SCHEDULERS=4
+
+# ./build/bin/vm/asm --no-sa net_server.asm
+./build/bin/vm/asm net_server.asm
+./build/bin/vm/kernel a.out "$@"

M sample/asm/exceptions/terminating_processes.asm => sample/asm/exceptions/terminating_processes.asm +6 -3
@@ 35,7 35,7 @@
 .end
 
 .function: cycle_burner/2
-    allocate_registers %2 local
+    allocate_registers %3 local
 
     ; burn as many cycles as are requested
     ; preferably there are as many cycles to burn through as to give the


@@ 48,8 48,11 @@
 
     ; print hello to the screen to show that the process #n just finished running
     ; where #n is the "ID" assigned by the caller
-    echo (string %1 local "Hello World from process ") local
-    print (move %1 local %0 parameters) local
+    text %1 local "Hello World from process "
+    move %2 local %0 parameters
+    text %2 local %2 local
+    textconcat %1 local %1 local %2 local
+    print %1 local
 
     return
 .end

A src/assembler/backend/op_assemblers/assemble_op_io_wait.cpp => src/assembler/backend/op_assemblers/assemble_op_io_wait.cpp +57 -0
@@ 0,0 1,57 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <iostream>
+#include <viua/assembler/backend/op_assemblers.h>
+
+namespace viua { namespace assembler { namespace backend {
+namespace op_assemblers {
+auto assemble_op_io_wait(Program& program,
+                         std::vector<Token> const& tokens,
+                         Token_index const i) -> void {
+    Token_index target        = i + 1;
+    Token_index source        = target + 2;
+    Token_index timeout_index = source + 2;
+
+    int_op target_operand;
+    int_op source_operand;
+    if (tokens.at(target) == "void") {
+        --source;
+        --timeout_index;
+        target_operand = ::assembler::operands::getint(
+            ::assembler::operands::resolve_register(tokens.at(target)));
+        source_operand = ::assembler::operands::getint_with_rs_type(
+            ::assembler::operands::resolve_register(tokens.at(source)),
+            ::assembler::operands::resolve_rs_type(tokens.at(source + 1)));
+    } else {
+        target_operand = ::assembler::operands::getint_with_rs_type(
+            ::assembler::operands::resolve_register(tokens.at(target)),
+            ::assembler::operands::resolve_rs_type(tokens.at(target + 1)));
+        source_operand = ::assembler::operands::getint_with_rs_type(
+            ::assembler::operands::resolve_register(tokens.at(source)),
+            ::assembler::operands::resolve_rs_type(tokens.at(source + 1)));
+    }
+
+    auto const timeout =
+        ::assembler::operands::convert_token_to_timeout_operand(
+            tokens.at(timeout_index));
+
+    program.op_io_wait(target_operand, source_operand, timeout);
+}
+}}}}  // namespace viua::assembler::backend::op_assemblers

A src/assembler/frontend/static_analyser/checkers/check_op_io_cancel.cpp => src/assembler/frontend/static_analyser/checkers/check_op_io_cancel.cpp +42 -0
@@ 0,0 1,42 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <viua/assembler/frontend/static_analyser.h>
+
+using viua::assembler::frontend::parser::Instruction;
+
+namespace viua { namespace assembler { namespace frontend {
+namespace static_analyser { namespace checkers {
+auto check_op_io_cancel(Register_usage_profile& register_usage_profile,
+                       Instruction const& instruction) -> void {
+    using viua::assembler::frontend::parser::Void_literal;
+
+    auto source = get_operand<Register_index>(instruction, 0);
+    if (not source) {
+        throw invalid_syntax(instruction.operands.at(0)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_use_of_register(register_usage_profile, *source);
+    assert_type_of_register<viua::internals::Value_types::IO_REQUEST>(
+        register_usage_profile, *source);
+}
+}}}}}  // namespace viua::assembler::frontend::static_analyser::checkers

A src/assembler/frontend/static_analyser/checkers/check_op_io_close.cpp => src/assembler/frontend/static_analyser/checkers/check_op_io_close.cpp +42 -0
@@ 0,0 1,42 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <viua/assembler/frontend/static_analyser.h>
+
+using viua::assembler::frontend::parser::Instruction;
+
+namespace viua { namespace assembler { namespace frontend {
+namespace static_analyser { namespace checkers {
+auto check_op_io_close(Register_usage_profile& register_usage_profile,
+                       Instruction const& instruction) -> void {
+    using viua::assembler::frontend::parser::Void_literal;
+
+    auto source = get_operand<Register_index>(instruction, 0);
+    if (not source) {
+        throw invalid_syntax(instruction.operands.at(0)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_use_of_register(register_usage_profile, *source);
+    assert_type_of_register<viua::internals::Value_types::IO_PORT>(
+        register_usage_profile, *source);
+}
+}}}}}  // namespace viua::assembler::frontend::static_analyser::checkers

A src/assembler/frontend/static_analyser/checkers/check_op_io_read.cpp => src/assembler/frontend/static_analyser/checkers/check_op_io_read.cpp +66 -0
@@ 0,0 1,66 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <viua/assembler/frontend/static_analyser.h>
+
+using viua::assembler::frontend::parser::Instruction;
+
+namespace viua { namespace assembler { namespace frontend {
+namespace static_analyser { namespace checkers {
+auto check_op_io_read(Register_usage_profile& register_usage_profile,
+                       Instruction const& instruction) -> void {
+    using viua::assembler::frontend::parser::Void_literal;
+
+    auto result = get_operand<Register_index>(instruction, 0);
+    if (not result) {
+        throw invalid_syntax(instruction.operands.at(0)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_if_name_resolved(register_usage_profile, *result);
+
+    auto source = get_operand<Register_index>(instruction, 1);
+    if (not source) {
+        throw invalid_syntax(instruction.operands.at(1)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_use_of_register(register_usage_profile, *source);
+    assert_type_of_register<viua::internals::Value_types::IO_PORT_LIKE>(
+        register_usage_profile, *source);
+
+    auto value = get_operand<Register_index>(instruction, 2);
+    if (not value) {
+        throw invalid_syntax(instruction.operands.at(2)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_use_of_register(register_usage_profile, *value);
+    assert_type_of_register<viua::internals::Value_types::UNDEFINED>(
+        register_usage_profile, *value);
+
+    auto val       = Register(*result);
+    val.value_type = Value_types::IO_REQUEST;
+    register_usage_profile.define(val, result->tokens.at(0));
+}
+}}}}}  // namespace viua::assembler::frontend::static_analyser::checkers

A src/assembler/frontend/static_analyser/checkers/check_op_io_wait.cpp => src/assembler/frontend/static_analyser/checkers/check_op_io_wait.cpp +61 -0
@@ 0,0 1,61 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <viua/assembler/frontend/static_analyser.h>
+
+using viua::assembler::frontend::parser::Instruction;
+
+namespace viua { namespace assembler { namespace frontend {
+namespace static_analyser { namespace checkers {
+auto check_op_io_wait(Register_usage_profile& register_usage_profile,
+                       Instruction const& instruction) -> void {
+    using viua::assembler::frontend::parser::Void_literal;
+
+    auto result = get_operand<Register_index>(instruction, 0);
+    if (not result) {
+        if (not get_operand<Void_literal>(instruction, 0)) {
+            throw invalid_syntax(instruction.operands.at(0)->tokens,
+                                 "invalid operand")
+                .note("expected register index or void");
+        }
+    }
+
+    if (result) {
+        check_if_name_resolved(register_usage_profile, *result);
+    }
+
+    auto source = get_operand<Register_index>(instruction, 1);
+    if (not source) {
+        throw invalid_syntax(instruction.operands.at(1)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_use_of_register(register_usage_profile, *source);
+    assert_type_of_register<viua::internals::Value_types::IO_REQUEST>(
+        register_usage_profile, *source);
+
+    if (result) {
+        auto val       = Register(*result);
+        val.value_type = Value_types::UNDEFINED;
+        register_usage_profile.define(val, result->tokens.at(0));
+    }
+}
+}}}}}  // namespace viua::assembler::frontend::static_analyser::checkers

A src/assembler/frontend/static_analyser/checkers/check_op_io_write.cpp => src/assembler/frontend/static_analyser/checkers/check_op_io_write.cpp +66 -0
@@ 0,0 1,66 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <viua/assembler/frontend/static_analyser.h>
+
+using viua::assembler::frontend::parser::Instruction;
+
+namespace viua { namespace assembler { namespace frontend {
+namespace static_analyser { namespace checkers {
+auto check_op_io_write(Register_usage_profile& register_usage_profile,
+                       Instruction const& instruction) -> void {
+    using viua::assembler::frontend::parser::Void_literal;
+
+    auto result = get_operand<Register_index>(instruction, 0);
+    if (not result) {
+        throw invalid_syntax(instruction.operands.at(0)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_if_name_resolved(register_usage_profile, *result);
+
+    auto source = get_operand<Register_index>(instruction, 1);
+    if (not source) {
+        throw invalid_syntax(instruction.operands.at(1)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_use_of_register(register_usage_profile, *source);
+    assert_type_of_register<viua::internals::Value_types::IO_PORT_LIKE>(
+        register_usage_profile, *source);
+
+    auto value = get_operand<Register_index>(instruction, 2);
+    if (not value) {
+        throw invalid_syntax(instruction.operands.at(2)->tokens,
+                             "invalid operand")
+            .note("expected register index");
+    }
+
+    check_use_of_register(register_usage_profile, *value);
+    assert_type_of_register<viua::internals::Value_types::UNDEFINED>(
+        register_usage_profile, *value);
+
+    auto val       = Register(*result);
+    val.value_type = Value_types::IO_REQUEST;
+    register_usage_profile.define(val, result->tokens.at(0));
+}
+}}}}}  // namespace viua::assembler::frontend::static_analyser::checkers

M src/assembler/frontend/static_analyser/checkers/utils.cpp => src/assembler/frontend/static_analyser/checkers/utils.cpp +12 -0
@@ 323,6 323,18 @@ auto const value_type_names = std::map<Value_types, std::string>{
         Value_types::OBJECT,
         "object",
     },
+    {
+        Value_types::IO_REQUEST,
+        "I/O request",
+    },
+    {
+        Value_types::IO_PORT,
+        "I/O port",
+    },
+    {
+        Value_types::IO_PORT_LIKE,
+        "I/O port-like",
+    },
 };
 auto operator|(const Value_types lhs, const Value_types rhs) -> Value_types {
     // FIXME find out if it is possible to remove the outermost static_cast<>

M src/assembler/frontend/static_analyser/register_usage.cpp => src/assembler/frontend/static_analyser/register_usage.cpp +15 -0
@@ 478,6 478,21 @@ auto check_register_usage_for_instruction_block_impl(
             case STRUCTKEYS:
                 check_op_structkeys(register_usage_profile, *instruction);
                 break;
+            case IO_READ:
+                check_op_io_read(register_usage_profile, *instruction);
+                break;
+            case IO_WRITE:
+                check_op_io_write(register_usage_profile, *instruction);
+                break;
+            case IO_CLOSE:
+                check_op_io_close(register_usage_profile, *instruction);
+                break;
+            case IO_WAIT:
+                check_op_io_wait(register_usage_profile, *instruction);
+                break;
+            case IO_CANCEL:
+                check_op_io_cancel(register_usage_profile, *instruction);
+                break;
             case RETURN:
                 // do nothing
                 break;

M src/cg/bytecode/instructions.cpp => src/cg/bytecode/instructions.cpp +33 -0
@@ 1134,6 1134,39 @@ auto opstructkeys(viua::internals::types::byte* addr_ptr,
     return insert_two_ri_instruction(addr_ptr, STRUCTKEYS, target, source);
 }
 
+auto op_io_read(viua::internals::types::byte* addr_ptr, int_op req, int_op port, int_op limit)
+    -> viua::internals::types::byte* {
+    return insert_three_ri_instruction(addr_ptr, IO_READ, req, port, limit);
+}
+auto op_io_write(viua::internals::types::byte* addr_ptr, int_op req, int_op port, int_op data)
+    -> viua::internals::types::byte* {
+    return insert_three_ri_instruction(addr_ptr, IO_WRITE, req, port, data);
+}
+auto op_io_close(viua::internals::types::byte* addr_ptr, int_op req, int_op port)
+    -> viua::internals::types::byte* {
+    return insert_two_ri_instruction(addr_ptr, IO_CLOSE, req, port);
+}
+auto op_io_wait(viua::internals::types::byte* addr_ptr, int_op result, int_op req, timeout_op limit)
+    -> viua::internals::types::byte* {
+    addr_ptr = insert_two_ri_instruction(addr_ptr, IO_WAIT, result, req);
+
+    // FIXME change to OT_TIMEOUT?
+    *(reinterpret_cast<OperandType*>(addr_ptr)) = OT_INT;
+    viua::support::pointer::inc<OperandType, viua::internals::types::byte>(
+        addr_ptr);
+
+    aligned_write(addr_ptr) = limit.value;
+    viua::support::pointer::inc<viua::internals::types::timeout,
+                                viua::internals::types::byte>(addr_ptr);
+
+    return addr_ptr;
+}
+auto op_io_cancel(viua::internals::types::byte* addr_ptr, int_op port)
+    -> viua::internals::types::byte* {
+    *(addr_ptr++) = IO_CANCEL;
+    return insert_ri_operand(addr_ptr, port);
+}
+
 auto opreturn(viua::internals::types::byte* addr_ptr)
     -> viua::internals::types::byte* {
     *(addr_ptr++) = RETURN;

M src/cg/disassembler/disassembler.cpp => src/cg/disassembler/disassembler.cpp +38 -0
@@ 754,6 754,44 @@ auto disassembler::instruction(viua::internals::types::byte* ptr)
                                     viua::internals::types::byte>(ptr);
 
         break;
+    case IO_READ:
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // request
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // port
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // limit or void
+
+        break;
+    case IO_WRITE:
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // request
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // port
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // byte vector
+
+        break;
+    case IO_CLOSE:
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // request
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // port
+
+        break;
+    case IO_WAIT:
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // result
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // request
+
+        viua::support::pointer::inc<viua::internals::types::byte,
+                                    viua::internals::types::byte>(ptr);
+
+        oss << ' ';
+        if (decode_timeout(ptr)) {
+            oss << decode_timeout(ptr) - 1 << "ms";
+        } else {
+            oss << "infinity";
+        }
+        viua::support::pointer::inc<viua::internals::types::timeout,
+                                    viua::internals::types::byte>(ptr);
+
+        break;
+    case IO_CANCEL:
+        ptr = disassemble_ri_operand_with_rs_type(oss, ptr);    // request
+
+        break;
     default:
         // if opcode was not covered here, it means it must have been a
         // variable-length opcode

M src/cg/tools.cpp => src/cg/tools.cpp +38 -0
@@ 958,6 958,29 @@ static auto size_of_insert =
     size_of_instruction_with_three_ri_operands_with_rs_types;
 static auto size_of_remove =
     size_of_instruction_with_three_ri_operands_with_rs_types;
+
+static auto size_of_io_read = size_of_instruction_with_three_ri_operands_with_rs_types;
+static auto size_of_io_write = size_of_instruction_with_three_ri_operands_with_rs_types;
+static auto size_of_io_close = size_of_instruction_with_two_ri_operands_with_rs_types;
+static auto size_of_io_wait(TokenVector const& tokens, TokenVector::size_type i)
+    -> tuple<bytecode_size_type, decltype(i)> {
+    auto calculated_size = bytecode_size_type{0};
+    std::tie(calculated_size, i) =
+        size_of_instruction_with_two_ri_operands_with_rs_types(tokens, i);
+
+    if (str::is_timeout_literal(tokens.at(i))) {
+        calculated_size += sizeof(viua::internals::types::byte);
+        calculated_size += sizeof(viua::internals::types::timeout);
+        ++i;
+    } else {
+        throw viua::cg::lex::Invalid_syntax(
+            tokens.at(i), "invalid timeout token in 'io_wait'");
+    }
+
+    return std::tuple<bytecode_size_type, decltype(i)>(calculated_size, i);
+}
+static auto size_of_io_cancel = size_of_instruction_with_one_ri_operand_with_rs_type;
+
 static auto size_of_return(TokenVector const&, TokenVector::size_type i)
     -> tuple<bytecode_size_type, decltype(i)> {
     auto calculated_size = bytecode_size_type{


@@ 1405,6 1428,21 @@ auto calculate_bytecode_size_of_first_n_instructions2(
         } else if (tokens.at(i) == "remove") {
             ++i;
             tie(increase, i) = size_of_remove(tokens, i);
+        } else if (tokens.at(i) == "io_read") {
+            ++i;
+            std::tie(increase, i) = size_of_io_read(tokens, i);
+        } else if (tokens.at(i) == "io_write") {
+            ++i;
+            std::tie(increase, i) = size_of_io_write(tokens, i);
+        } else if (tokens.at(i) == "io_close") {
+            ++i;
+            std::tie(increase, i) = size_of_io_close(tokens, i);
+        } else if (tokens.at(i) == "io_wait") {
+            ++i;
+            std::tie(increase, i) = size_of_io_wait(tokens, i);
+        } else if (tokens.at(i) == "io_cancel") {
+            ++i;
+            std::tie(increase, i) = size_of_io_cancel(tokens, i);
         } else if (tokens.at(i) == "return") {
             ++i;
             tie(increase, i) = size_of_return(tokens, i);

M src/front/asm/assemble_instruction.cpp => src/front/asm/assemble_instruction.cpp +11 -0
@@ 551,6 551,17 @@ auto assemble_instruction(
         assemble_op_structat(program, tokens, i);
     } else if (tokens.at(i) == "structkeys") {
         assemble_double_register_op<&Program::opstructkeys>(program, tokens, i);
+    } else if (tokens.at(i) == "io_read") {
+        assemble_three_register_op<&Program::op_io_read>(program, tokens, i);
+    } else if (tokens.at(i) == "io_write") {
+        assemble_three_register_op<&Program::op_io_write>(program, tokens, i);
+    } else if (tokens.at(i) == "io_close") {
+        assemble_double_register_op<&Program::op_io_close>(program, tokens, i);
+    } else if (tokens.at(i) == "io_wait") {
+        viua::assembler::backend::op_assemblers::assemble_op_io_wait(
+            program, tokens, i);
+    } else if (tokens.at(i) == "io_cancel") {
+        assemble_single_register_op<&Program::op_io_cancel>(program, tokens, i);
     } else if (tokens.at(i) == "return") {
         program.opreturn();
     } else if (tokens.at(i) == "halt") {

M src/front/kernel.cpp => src/front/kernel.cpp +5 -1
@@ 49,6 49,7 @@ static auto display_vm_information(bool const verbose) -> void {
     auto const full_version = std::string{VERSION} + "." + MICRO;
     auto const proc_schedulers = viua::kernel::Kernel::no_of_process_schedulers();
     auto const ffi_schedulers = viua::kernel::Kernel::no_of_ffi_schedulers();
+    auto const io_schedulers = viua::kernel::Kernel::no_of_io_schedulers();
     auto const cpus_available = std::thread::hardware_concurrency();
 
     std::cerr << "Viua VM " << full_version << " [";


@@ 60,7 61,10 @@ static auto display_vm_information(bool const verbose) -> void {
     std::cerr << proc_schedulers << ':';
 
     if (verbose) { std::cerr << "ffi="; }
-    std::cerr << ffi_schedulers;
+    std::cerr << ffi_schedulers << ':';
+
+    if (verbose) { std::cerr << "io="; }
+    std::cerr << io_schedulers;
 
     std::cerr << "]\n";
 }

M src/kernel/kernel.cpp => src/kernel/kernel.cpp +142 -32
@@ 37,12 37,14 @@
 #include <viua/scheduler/ffi.h>
 #include <viua/scheduler/vps.h>
 #include <viua/scheduler/process.h>
+#include <viua/scheduler/io.h>
 #include <viua/support/env.h>
 #include <viua/support/pointer.h>
 #include <viua/support/string.h>
 #include <viua/types/exception.h>
 #include <viua/types/function.h>
 #include <viua/types/integer.h>
+#include <viua/types/io.h>
 #include <viua/types/object.h>
 #include <viua/types/reference.h>
 #include <viua/types/string.h>


@@ 509,6 511,78 @@ uint64_t viua::kernel::Kernel::pids() const {
     return running_processes;
 }
 
+auto viua::kernel::Kernel::schedule_io(std::unique_ptr<viua::scheduler::io::IO_interaction> i)
+    -> void {
+    {
+        std::unique_lock<std::mutex> lck { io_requests_mtx };
+        io_requests.insert({ i->id(), i.get() });
+    }
+    {
+        std::unique_lock<std::mutex> lck { io_request_mutex };
+        io_request_queue.push_back(std::move(i));
+    }
+    io_request_cv.notify_one();
+}
+auto viua::kernel::Kernel::cancel_io(std::tuple<uint64_t, uint64_t> const interaction_id)
+    -> void {
+    std::unique_lock<std::mutex> lck { io_requests_mtx };
+    /*
+     * We have to check if the request is still there to avoid crashing when the
+     * cancellation order arrives just after the request has been completed, as
+     * request slots are removed after completion.
+     */
+    // FIXME Maybe change the I/O pipeline so that the .count() call is not
+    // needed? A better correctness guarantee would be much more reasurring than
+    // a "see if it's there" bandaid. Maybe don't erase the request slot until
+    // after the process has waited for the request in question?
+    if (io_requests.count(interaction_id)) {
+        io_requests.at(interaction_id)->cancel();
+    }
+}
+auto viua::kernel::Kernel::io_complete(std::tuple<uint64_t, uint64_t> const interaction_id) const -> bool {
+    std::unique_lock<std::mutex> lck { io_result_mtx };
+    if (not io_results.count(interaction_id)) {
+        return false;
+    }
+    return io_results.at(interaction_id).is_complete;
+}
+auto viua::kernel::Kernel::complete_io(std::tuple<uint64_t, uint64_t> const interaction_id, IO_result result) -> void {
+    {
+        std::unique_lock<std::mutex> lck { io_requests_mtx };
+        io_requests.erase(io_requests.find(interaction_id));
+    }
+    std::unique_lock<std::mutex> lck { io_result_mtx };
+    io_results.insert({ interaction_id, std::move(result) });
+}
+auto viua::kernel::Kernel::io_result(std::tuple<uint64_t, uint64_t> const interaction_id)
+    -> std::unique_ptr<viua::types::Value> {
+    std::unique_lock<std::mutex> lck { io_result_mtx };
+    auto result = std::move(io_results.at(interaction_id));
+    io_results.erase(io_results.find(interaction_id));
+    lck.unlock();
+
+    if (result.is_successful) {
+        return std::move(result.value);
+    }
+    throw std::move(result.error);
+}
+
+viua::kernel::Kernel::IO_result::IO_result(bool const ok, std::unique_ptr<viua::types::Value> x)
+    : value{ok ? std::move(x) : nullptr}
+    , error{ok ? nullptr : std::move(x)}
+    , is_complete{true}
+    , is_cancelled{false}
+    , is_successful{ok}
+{}
+auto viua::kernel::Kernel::IO_result::make_success(std::unique_ptr<viua::types::Value> x)
+    -> IO_result {
+    return IO_result{true, std::move(x)};
+}
+auto viua::kernel::Kernel::IO_result::make_error(std::unique_ptr<viua::types::Value> x)
+    -> IO_result {
+    return IO_result{false, std::move(x)};
+}
+
 int viua::kernel::Kernel::exit() const {
     return return_code;
 }


@@ 540,6 614,13 @@ auto viua::kernel::Kernel::no_of_ffi_schedulers()
     return no_of_schedulers("VIUA_FFI_SCHEDULERS",
                             static_cast<schedulers_count>(default_value));
 }
+auto viua::kernel::Kernel::no_of_io_schedulers()
+    -> viua::internals::types::schedulers_count {
+    auto const default_value = (std::thread::hardware_concurrency() / 2);
+    using viua::internals::types::schedulers_count;
+    return no_of_schedulers("VIUA_IO_SCHEDULERS",
+                            static_cast<schedulers_count>(default_value));
+}
 auto viua::kernel::Kernel::is_tracing_enabled() -> bool {
     auto viua_enable_tracing = std::string{};
     char* env_text           = getenv("VIUA_ENABLE_TRACING");


@@ 649,41 730,70 @@ viua::kernel::Kernel::Kernel()
                                      &foreign_call_queue_mutex,
                                      &foreign_call_queue_condition));
     }
+
+    auto const io_schedulers_limit = no_of_io_schedulers();
+    for (auto i = io_schedulers_limit; i; --i) {
+        io_workers.emplace_back(
+            std::make_unique<std::thread>(viua::scheduler::io::io_scheduler,
+                (io_schedulers_limit - i),
+                std::ref(*this),
+                std::ref(io_request_queue),
+                std::ref(io_request_mutex),
+                std::ref(io_request_cv)));
+    }
 }
 
 viua::kernel::Kernel::~Kernel() {
-    /** Send a poison pill to every foreign function call worker thread.
-     *  Collect them after they are killed.
-     *
-     * Use std::defer_lock to preven constructor from acquiring the mutex
-     * .lock() is called manually later
-     */
-    std::unique_lock<std::mutex> lck(foreign_call_queue_mutex, std::defer_lock);
-    for (auto i = foreign_call_workers.size(); i; --i) {
-        // acquire the mutex for foreign call request queue
-        lck.lock();
-
-        // send poison pill;
-        // one per worker thread since we can be sure that a thread consumes at
-        // most one pill
-        foreign_call_queue.push_back(nullptr);
-
-        // release the mutex and notify worker thread that there is work to do
-        // the thread consumes the pill and aborts
-        lck.unlock();
-        foreign_call_queue_condition.notify_all();
-    }
-    while (not foreign_call_workers.empty()) {
-        // fetch handle for worker thread and
-        // remove it from the list of workers
-        auto w = std::move(foreign_call_workers.back());
-        foreign_call_workers.pop_back();
-
-        // join worker back to main thread and
-        // delete it
-        // by now, all workers should be killed by poison pills we sent them
-        // earlier
-        w->join();
+    {
+        /*
+         * Send a poison pill to every foreign function call worker thread.
+         * Collect them after they are killed.
+         */
+        std::unique_lock<std::mutex> lck {foreign_call_queue_mutex, std::defer_lock};
+        for (auto i = foreign_call_workers.size(); i; --i) {
+            lck.lock();
+
+            /*
+             * Send the actual poison pill. One per worker thread since we can
+             * be sure that a thread consumes at most one pill.
+             */
+            foreign_call_queue.push_back(nullptr);
+
+            lck.unlock();
+            foreign_call_queue_condition.notify_all();
+        }
+        while (not foreign_call_workers.empty()) {
+            auto w = std::move(foreign_call_workers.back());
+            foreign_call_workers.pop_back();
+            w->join();
+        }
+    }
+    {
+        /*
+         * Send a poison pill to every I/O worker thread.
+         */
+        if constexpr (false) {
+            std::cerr
+                << "[kernel] starting I/O shutdown, "
+                << io_workers.size()
+                << " workers to kill\n";
+        }
+        {
+            std::unique_lock<std::mutex> lck {io_request_mutex};
+            for (auto const& _[[maybe_unused]] : io_workers) {
+                io_request_queue.push_back(nullptr);
+            }
+        }
+        io_request_cv.notify_all();
+        for (auto& each : io_workers) {
+            if constexpr (false) {
+                std::cerr << "[kernel] waiting for I/O worker\n";
+            }
+            each->join();
+        }
+        if constexpr (false) {
+            std::cerr << "[kernel] done with I/O shutdown\n";
+        }
     }
 
     for (auto const each : cxx_dynamic_lib_handles) {

M src/kernel/registerset.cpp => src/kernel/registerset.cpp +2 -11
@@ 333,21 333,12 @@ void viua::kernel::Register_set::clear(
 bool viua::kernel::Register_set::isflagged(
     viua::internals::types::register_index index,
     mask_type filter) {
-    /** Returns true if given filter is enabled for register specified by given
-     * index. Returns false otherwise.
-     *
-     *  Performs bounds checking.
-     */
     if (index >= registerset_size) {
+        // FIXME Use tagged exception.
         throw make_unique<viua::types::Exception>(
             "register access out of bounds: mask_isenabled");
     }
-    // FIXME: should throw when accessing empty register, but that breaks set()
-    if (index >= registers.size()) {
-        return registers.at(index).is_flagged(filter);
-    } else {
-        return false;
-    }
+    return registers.at(index).is_flagged(filter);
 }
 
 void viua::kernel::Register_set::setmask(

M src/process.cpp => src/process.cpp +32 -16
@@ 1,5 1,5 @@
 /*
- *  Copyright (C) 2015, 2016, 2017, 2018 Marek Marecki
+ *  Copyright (C) 2015-2019 Marek Marecki
  *
  *  This file is part of Viua VM.
  *


@@ 29,6 29,7 @@
 #include <viua/types/integer.h>
 #include <viua/types/process.h>
 #include <viua/types/reference.h>
+#include <viua/types/io.h>
 using namespace std;
 
 // Provide storage for static member.


@@ 210,25 211,32 @@ auto viua::process::Process::tick() -> Op_address_type {
         return nullptr;
     }
 
-    /*  Machine should halt execution if previous instruction pointer is the
-     * same as current one as it means that the execution flow is corrupted and
-     *  entered an infinite loop.
+    /*
+     * Machine should halt execution if previous instruction pointer is the same
+     * as current one as it means that the execution flow is corrupted and
+     * entered an infinite loop.
      *
-     *  However, execution *should not* be halted if:
-     *      - the offending opcode is RETURN (as this may indicate exiting
-     * recursive function),
-     *      - the offending opcode is JOIN (as this means that a process is
-     * waiting for another process to finish),
-     *      - the offending opcode is RECEIVE (as this means that a process is
-     * waiting for a message),
-     *      - an object has been thrown, as the instruction pointer will be
-     * adjusted by catchers or execution will be halted on unhandled types,
+     * However, execution *should not* be halted if:
+     * - the offending opcode is RETURN (as this may indicate exiting recursive
+     *   function)
+     * - the offending opcode is JOIN (as this means that a process is waiting
+     *   for another process to finish)
+     * - the offending opcode is RECEIVE (as this means that a process is
+     *   waiting for a message)
+     * - the offending opcode is IO_WAIT (as this means that a process is
+     *   waiting for I/O to complete)
+     * - an object has been thrown, as the instruction pointer will be adjusted
+     *   by catchers or execution will be halted on unhandled types
      */
+    auto const allowed_unchanged_ops = std::set<OPCODE>{
+        RETURN,
+        JOIN,
+        RECEIVE,
+        IO_WAIT,
+    };
     if (stack->instruction_pointer == previous_instruction_pointer
         and stack->state_of() == viua::process::Stack::STATE::RUNNING
-        and (OPCODE(*stack->instruction_pointer) != RETURN
-             and OPCODE(*stack->instruction_pointer) != JOIN
-             and OPCODE(*stack->instruction_pointer) != RECEIVE)
+        and (not allowed_unchanged_ops.count(OPCODE(*stack->instruction_pointer)))
         and (not stack->thrown)) {
         stack->thrown =
             make_unique<viua::types::Exception>("InstructionUnchanged");


@@ 409,6 417,14 @@ bool viua::process::Process::empty() const {
     return message_queue.empty();
 }
 
+auto viua::process::Process::schedule_io(std::unique_ptr<viua::scheduler::io::IO_interaction> i)
+    -> void {
+    attached_scheduler->schedule_io(std::move(i));
+}
+auto viua::process::Process::cancel_io(std::tuple<uint64_t, uint64_t> const interaction_id) -> void {
+    attached_scheduler->cancel_io(interaction_id);
+}
+
 void viua::process::Process::migrate_to(
     viua::scheduler::Process_scheduler* sch) {
     attached_scheduler = sch;

M src/process/dispatch.cpp => src/process/dispatch.cpp +15 -0
@@ 472,6 472,21 @@ auto viua::process::Process::dispatch(viua::internals::types::byte const* addr)
     case NOP:
         ++addr;
         break;
+    case IO_READ:
+        addr = op_io_read(addr + 1);
+        break;
+    case IO_WRITE:
+        addr = op_io_write(addr + 1);
+        break;
+    case IO_CLOSE:
+        addr = op_io_close(addr + 1);
+        break;
+    case IO_WAIT:
+        addr = op_io_wait(addr + 1);
+        break;
+    case IO_CANCEL:
+        addr = op_io_cancel(addr + 1);
+        break;
     case STREQ:
     case BOOL:
     case BITSWIDTH:

A src/process/instr/io.cpp => src/process/instr/io.cpp +168 -0
@@ 0,0 1,168 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <iostream>
+#include <memory>
+#include <viua/scheduler/process.h>
+#include <viua/process.h>
+#include <viua/bytecode/decoder/operands.h>
+#include <viua/types/value.h>
+#include <viua/types/integer.h>
+#include <viua/types/string.h>
+#include <viua/types/io.h>
+
+auto viua::process::Process::op_io_read(Op_address_type addr) -> Op_address_type {
+    viua::kernel::Register* target = nullptr;
+    std::tie(addr, target) =
+        viua::bytecode::decoder::operands::fetch_register(addr, this);
+
+    viua::kernel::Register* porty = nullptr;
+    std::tie(addr, porty) =
+        viua::bytecode::decoder::operands::fetch_register(addr, this);
+
+    viua::kernel::Register* limity = nullptr;
+    std::tie(addr, limity) =
+        viua::bytecode::decoder::operands::fetch_register(addr, this);
+
+    if (dynamic_cast<viua::types::Integer*>(porty->get())) {
+        auto port = std::make_unique<viua::types::IO_fd>(
+            static_cast<viua::types::Integer&>(*porty->get()).as_integer()
+            , viua::types::IO_fd::Ownership::Borrowed
+        );
+        *target = port->read(attached_scheduler->kernel(), limity->give());
+    } else if (dynamic_cast<viua::types::IO_fd*>(porty->get())) {
+        auto& port = static_cast<viua::types::IO_fd&>(*porty->get());
+        *target = port.read(attached_scheduler->kernel(), limity->give());
+    }
+
+    return addr;
+}
+
+auto viua::process::Process::op_io_write(Op_address_type addr) -> Op_address_type {
+    viua::kernel::Register* target = nullptr;
+    std::tie(addr, target) =
+        viua::bytecode::decoder::operands::fetch_register(addr, this);
+
+    viua::kernel::Register* porty = nullptr;
+    std::tie(addr, porty) =
+        viua::bytecode::decoder::operands::fetch_register(addr, this);
+
+    auto data = viua::util::memory::dumb_ptr<viua::kernel::Register>{nullptr};
+    std::tie(addr, data) =
+        viua::bytecode::decoder::operands::fetch_register(addr, this);
+
+    if (dynamic_cast<viua::types::Integer*>(porty->get())) {
+        auto port = std::make_unique<viua::types::IO_fd>(
+            static_cast<viua::types::Integer&>(*porty->get()).as_integer()
+            , viua::types::IO_fd::Ownership::Borrowed
+        );
+        *target = port->write(attached_scheduler->kernel(), data->give());
+    } else if (dynamic_cast<viua::types::IO_fd*>(porty->get())) {
+        auto& port = static_cast<viua::types::IO_fd&>(*porty->get());
+        *target = port.write(attached_scheduler->kernel(), data->give());
+    }
+
+    return addr;
+}
+
+auto viua::process::Process::op_io_close(Op_address_type addr) -> Op_address_type {
+    viua::types::IO_port* port = nullptr;
+    std::tie(addr, port) = viua::bytecode::decoder::operands::fetch_object_of<
+        std::remove_pointer<decltype(port)>::type>(addr, this);
+
+    port->close();
+
+    return addr;
+}
+
+auto viua::process::Process::op_io_wait(Op_address_type addr) -> Op_address_type {
+    /*
+     * Wait for an I/O request to complete or a timeout to pass. If the request
+     * completes in the specified time its result is returned in the destination
+     * register; otherwise an exception is thrown.
+     */
+
+    /*
+     * By default return to just before the instruction. This is the same dumb
+     * and inefficient solution that is used to implement timeouts for the
+     * receive instruction. However, this avoids global synchronisation so may
+     * not be that bad after all.
+     */
+    auto return_addr = Op_address_type{addr - 1};
+
+    auto target = viua::util::memory::dumb_ptr<viua::kernel::Register>{nullptr};
+    auto const target_is_void =
+        viua::bytecode::decoder::operands::is_void(addr);
+    if (not target_is_void) {
+        std::tie(addr, target) =
+            viua::bytecode::decoder::operands::fetch_register(addr, this);
+    } else {
+        addr = viua::bytecode::decoder::operands::fetch_void(addr);
+    }
+
+    viua::types::IO_request *request = nullptr;
+    std::tie(addr, request) = viua::bytecode::decoder::operands::fetch_object_of<
+        std::remove_pointer<decltype(request)>::type>(addr, this);
+
+    viua::internals::types::timeout timeout = 0;
+    std::tie(addr, timeout) =
+        viua::bytecode::decoder::operands::fetch_timeout(addr, this);
+
+    if (timeout and not timeout_active) {
+        waiting_until  = (std::chrono::steady_clock::now()
+                         + std::chrono::milliseconds(timeout - 1));
+        timeout_active = true;
+    } else if (not timeout and not timeout_active) {
+        wait_until_infinity = true;
+        timeout_active      = true;
+    }
+
+    if (attached_scheduler->io_complete(request->id())) {
+        auto result = attached_scheduler->io_result(request->id());
+        if (target) {
+            *target = std::move(result);
+        }
+
+        timeout_active = false;
+        wait_until_infinity = false;
+        return_addr = addr;
+    } else {
+        if (timeout_active and (not wait_until_infinity)
+            and (waiting_until < std::chrono::steady_clock::now())) {
+            timeout_active      = false;
+            wait_until_infinity = false;
+            stack->thrown =
+                std::make_unique<viua::types::Exception>("I/O not completed");
+            return_addr = addr;
+        }
+    }
+
+    return return_addr;
+}
+
+auto viua::process::Process::op_io_cancel(Op_address_type addr) -> Op_address_type {
+    viua::types::IO_request *request = nullptr;
+    std::tie(addr, request) = viua::bytecode::decoder::operands::fetch_object_of<
+        std::remove_pointer<decltype(request)>::type>(addr, this);
+
+    cancel_io(request->id());
+
+    return addr;
+}

M src/programinstructions.cpp => src/programinstructions.cpp +25 -0
@@ 802,6 802,31 @@ auto Program::opstructkeys(int_op const a, int_op const b) -> Program& {
     return (*this);
 }
 
+auto Program::op_io_read(int_op const req, int_op const port, int_op const limit) -> Program& {
+    addr_ptr = cg::bytecode::op_io_read(addr_ptr, req, port, limit);
+    return (*this);
+}
+
+auto Program::op_io_write(int_op const req, int_op const port, int_op const payload) -> Program& {
+    addr_ptr = cg::bytecode::op_io_write(addr_ptr, req, port, payload);
+    return (*this);
+}
+
+auto Program::op_io_close(int_op const req, int_op const port) -> Program& {
+    addr_ptr = cg::bytecode::op_io_close(addr_ptr, req, port);
+    return (*this);
+}
+
+auto Program::op_io_wait(int_op const result, int_op const req, timeout_op limit) -> Program& {
+    addr_ptr = cg::bytecode::op_io_wait(addr_ptr, result, req, limit);
+    return (*this);
+}
+
+auto Program::op_io_cancel(int_op const req) -> Program& {
+    addr_ptr = cg::bytecode::op_io_cancel(addr_ptr, req);
+    return (*this);
+}
+
 auto Program::opreturn() -> Program& {
     addr_ptr = cg::bytecode::opreturn(addr_ptr);
     return (*this);

A src/scheduler/io/request.cpp => src/scheduler/io/request.cpp +18 -0
@@ 0,0 1,18 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */

A src/scheduler/io/scheduler.cpp => src/scheduler/io/scheduler.cpp +181 -0
@@ 0,0 1,181 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <chrono>
+#include <condition_variable>
+#include <iostream>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+#include <viua/kernel/kernel.h>
+#include <viua/scheduler/io.h>
+#include <viua/scheduler/io/interactions.h>
+#include <viua/types/exception.h>
+#include <viua/types/io.h>
+
+static auto is_sentinel(std::unique_ptr<viua::scheduler::io::IO_interaction> const& item) -> bool {
+    return (item.get() == nullptr);
+}
+
+void viua::scheduler::io::io_scheduler(
+    uint64_t const scheduler_id,
+    viua::kernel::Kernel& kernel,
+    std::deque<std::unique_ptr<IO_interaction>>& requests,
+    std::mutex& io_request_mutex,
+    std::condition_variable& io_request_cv) {
+
+    auto local_interactions = std::deque<std::unique_ptr<IO_interaction>>{};
+
+    while (true) {
+        std::unique_ptr<IO_interaction> interaction;
+        {
+            std::unique_lock<std::mutex> lck { io_request_mutex };
+
+            /*
+             *             WARNING! ACHTUNG! HERE BE DRAGONS!
+             *
+             * Do not try to simplify the condition by removing the seemingly
+             * redundant `not`s - they are not! With them both removed the condition
+             * encodes the same logic, but for some reason the VM will hang.
+             * It looks like one thread is monopolising the lock (maybe it is
+             * running in too tight a loop?) which leads to starvation of other
+             * threads who never get to lock it... including the main kernel thread.
+             */
+            auto const timeout = std::chrono::milliseconds{local_interactions.empty() ? 10 : 0};
+            while (not io_request_cv.wait_for(lck, timeout, [&requests, &local_interactions]{
+                return (not (requests.empty() and local_interactions.empty()));
+            }));
+
+            interaction = std::move(requests.front());
+            requests.pop_front();
+        }
+
+        if (is_sentinel(interaction)) {
+            if constexpr (false) {
+                std::cerr <<
+                    ("[io][id=" + std::to_string(scheduler_id) + "] received sentinel\n");
+            }
+            break;
+        }
+
+        if (not interaction->fd().has_value()) {
+            /*
+             * Do not work with interactions that do not expose file
+             * descriptors. Just fail them. Maybe next time they will behave
+             * better.
+             */
+            kernel.complete_io(
+                interaction->id()
+                , viua::kernel::Kernel::IO_result::make_error(std::make_unique<viua::types::Exception>(
+                    "IO_without_fd"
+                    , "I/O port did not expose file descriptor"
+                ))
+            );
+            continue;
+        }
+
+        {
+            auto& work = *interaction;
+
+            fd_set readfds;
+            fd_set writefds;
+            FD_ZERO(&readfds);
+            FD_ZERO(&writefds);
+
+            switch (work.kind()) {
+                case IO_kind::Input:
+                    FD_SET(*work.fd(), &readfds);
+                    break;
+                case IO_kind::Output:
+                    FD_SET(*work.fd(), &writefds);
+                    break;
+                default:
+                    /*
+                     * Doing nothing is harmless in this situation.
+                     */
+                    break;
+            }
+
+            timeval timeout;
+            memset(&timeout, 0, sizeof(timeout));
+            timeout.tv_sec = 0;
+            timeout.tv_usec = 1;
+
+            auto const s = select(*work.fd() + 1, &readfds, &writefds, nullptr, &timeout);
+            if (s == -1) {
+                auto const saved_errno = errno;
+                if constexpr (false) {
+                    std::cerr << (
+                        "[io][id=" + std::to_string(scheduler_id)
+                        + "] select(3) error: " + std::to_string(saved_errno)
+                        + "\n");
+                }
+                kernel.schedule_io(std::move(interaction));
+                continue;
+            }
+            if (s == 0) {
+                if constexpr (false) {
+                    std::cerr << (
+                        "[io][id=" + std::to_string(scheduler_id)
+                        + "] select(3) returned 0 for "
+                        + (work.kind() == IO_kind::Input ? "input" : "output")
+                        + " interaction on fd " + std::to_string(*work.fd())
+                        + "\n");
+                }
+                kernel.schedule_io(std::move(interaction));
+                continue;
+            }
+
+            auto is_ready = false;
+            switch (work.kind()) {
+                case IO_kind::Input:
+                    is_ready = FD_ISSET(*work.fd(),  &readfds);
+                    break;
+                case IO_kind::Output:
+                    is_ready = FD_ISSET(*work.fd(), &writefds);
+                    break;
+                default:
+                    /* It is safe to do nothing in this case. */
+                    break;
+            }
+
+            if (not is_ready) {
+                kernel.schedule_io(std::move(interaction));
+                continue;
+            }
+
+            auto result = work.interact();
+            if (result.state == IO_interaction::State::Complete) {
+                kernel.complete_io(work.id(), (
+                    (result.status == IO_interaction::Status::Success)
+                    ? viua::kernel::Kernel::IO_result::make_success
+                    : viua::kernel::Kernel::IO_result::make_error
+                )(std::move(result.result)));
+            } else {
+                kernel.schedule_io(std::move(interaction));
+            }
+        }
+    }
+
+    if constexpr (false) {
+        std::cerr <<
+            ("[io][id=" + std::to_string(scheduler_id) + "] scheduler shutting down\n");
+    }
+}

M src/scheduler/process.cpp => src/scheduler/process.cpp +21 -1
@@ 26,10 26,11 @@
 #include <viua/process.h>
 #include <viua/types/exception.h>
 #include <viua/types/function.h>
+#include <viua/types/io.h>
 #include <viua/types/pointer.h>
-#include <viua/types/vector.h>
 #include <viua/types/string.h>
 #include <viua/types/struct.h>
+#include <viua/types/vector.h>
 #include <viua/scheduler/process.h>
 
 static auto print_stack_trace_default(viua::process::Process& process) -> void {


@@ 597,4 598,23 @@ auto Process_scheduler::join() -> void {
 auto Process_scheduler::exit() const -> int {
     return exit_code.value_or(0);
 }
+
+auto Process_scheduler::schedule_io(std::unique_ptr<viua::scheduler::io::IO_interaction> i)
+    -> void {
+    attached_kernel.schedule_io(std::move(i));
+}
+
+auto Process_scheduler::cancel_io(viua::scheduler::io::IO_interaction::id_type const interaction_id)
+    -> void {
+    attached_kernel.cancel_io(interaction_id);
+}
+
+auto Process_scheduler::io_complete(viua::scheduler::io::IO_interaction::id_type const interaction_id) const -> bool {
+    return attached_kernel.io_complete(interaction_id);
+}
+
+auto Process_scheduler::io_result(viua::scheduler::io::IO_interaction::id_type const interaction_id)
+    -> std::unique_ptr<viua::types::Value> {
+    return attached_kernel.io_result(interaction_id);
+}
 }}

A src/stdlib/posix/io.cpp => src/stdlib/posix/io.cpp +62 -0
@@ 0,0 1,62 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>  // for close(3), write(3), read(3)
+#include <iostream>
+#include <memory>
+#include <string_view>
+#include <viua/include/module.h>
+#include <viua/types/exception.h>
+#include <viua/types/integer.h>
+#include <viua/types/io.h>
+#include <viua/types/string.h>
+
+
+namespace viua { namespace stdlib { namespace posix { namespace io {
+template<typename T> auto memset(T& value, int const c) -> void {
+    ::memset(&value, c, sizeof(std::remove_reference_t<T>));
+}
+
+static auto open(Frame* frame,
+                   viua::kernel::Register_set*,
+                   viua::kernel::Register_set*,
+                   viua::process::Process*,
+                   viua::kernel::Kernel*) -> void {
+    auto const fd = ::open(frame->arguments->get(0)->str().c_str(), 0);
+    if (fd == -1) {
+        throw std::make_unique<viua::types::Exception>("Unknown_errno");
+    }
+
+    frame->set_local_register_set(
+        std::make_unique<viua::kernel::Register_set>(1));
+    frame->local_register_set->set(
+        0, std::make_unique<viua::types::IO_fd>(fd));
+}
+}}}}  // namespace viua::stdlib::posix::io
+
+const Foreign_function_spec functions[] = {
+    {"std::posix::io::open/1", &viua::stdlib::posix::io::open},
+    {nullptr, nullptr},
+};
+
+extern "C" const Foreign_function_spec* exports() {
+    return functions;
+}

M src/stdlib/posix/network.cpp => src/stdlib/posix/network.cpp +46 -40
@@ 1,5 1,5 @@
 /*
- *  Copyright (C) 2015, 2016, 2017, 2018 Marek Marecki
+ *  Copyright (C) 2019 Marek Marecki
  *
  *  This file is part of Viua VM.
  *


@@ 34,6 34,8 @@
 #include <viua/types/exception.h>
 #include <viua/types/integer.h>
 #include <viua/types/string.h>
+#include <viua/types/pointer.h>
+#include <viua/types/io.h>
 
 
 namespace viua { namespace stdlib { namespace posix { namespace network {


@@ 56,6 58,8 @@ template<typename T> auto memset(T& value, int const c) -> void {
     ::memset(&value, c, sizeof(std::remove_reference_t<T>));
 }
 
+using Socket_type = viua::types::IO_fd;
+
 static auto socket(Frame* frame,
                    viua::kernel::Register_set*,
                    viua::kernel::Register_set*,


@@ 161,7 165,10 @@ static auto socket(Frame* frame,
     frame->set_local_register_set(
         std::make_unique<viua::kernel::Register_set>(1));
     frame->local_register_set->set(
-        0, std::make_unique<viua::types::Integer>(sock));
+        0, std::make_unique<Socket_type>(
+            sock
+            , viua::types::IO_fd::Ownership::Owned
+        ));
 }
 
 static auto connect(Frame* frame,


@@ 178,10 185,9 @@ static auto connect(Frame* frame,
             ->as_integer()));
     addr.sin_addr.s_addr = inet_ston(frame->arguments->get(1)->str());
 
-    auto const sock =
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer();
-    if (::connect(static_cast<int>(sock),
+    auto const& sock =
+        static_cast<Socket_type&>(*frame->arguments->get(0));
+    if (::connect(sock.fd(),
                   reinterpret_cast<sockaddr*>(&addr),
                   sizeof(addr))
         == -1) {


@@ 310,10 316,9 @@ static auto bind(Frame* frame,
             ->as_integer()));
     addr.sin_addr.s_addr = inet_ston(frame->arguments->get(1)->str());
 
-    auto const sock =
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer();
-    if (::bind(static_cast<int>(sock),
+    auto const& sock =
+        static_cast<Socket_type&>(*frame->arguments->get(0));
+    if (::bind(sock.fd(),
                reinterpret_cast<sockaddr*>(&addr),
                sizeof(addr))
         == -1) {


@@ 426,6 431,10 @@ static auto bind(Frame* frame,
         throw std::make_unique<viua::types::Exception>(
             known_errors.at(error_number));
     }
+
+    frame->set_local_register_set(
+        std::make_unique<viua::kernel::Register_set>(1));
+    frame->local_register_set->set(0, std::move(frame->arguments->pop(0)));
 }
 
 static auto listen(Frame* frame,


@@ 433,13 442,12 @@ static auto listen(Frame* frame,
                    viua::kernel::Register_set*,
                    viua::process::Process*,
                    viua::kernel::Kernel*) -> void {
-    auto const sock =
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer();
+    auto const& sock =
+        static_cast<Socket_type&>(*frame->arguments->get(0));
     auto const backlog =
         static_cast<viua::types::Integer*>(frame->arguments->get(1))
             ->as_integer();
-    if (::listen(static_cast<int>(sock), static_cast<int>(backlog)) == -1) {
+    if (::listen(sock.fd(), static_cast<int>(backlog)) == -1) {
         auto const error_number = errno;
         auto const known_errors = std::map<decltype(error_number), std::string>{
             {


@@ 549,17 557,20 @@ static auto listen(Frame* frame,
         throw std::make_unique<viua::types::Exception>(
             known_errors.at(error_number));
     }
+
+    frame->set_local_register_set(
+        std::make_unique<viua::kernel::Register_set>(1));
+    frame->local_register_set->set(0, std::move(frame->arguments->pop(0)));
 }
 
 static auto accept(Frame* frame,
                    viua::kernel::Register_set*,
                    viua::kernel::Register_set*,
-                   viua::process::Process*,
+                   viua::process::Process* proc,
                    viua::kernel::Kernel*) -> void {
-    auto const sock =
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer();
-    auto const incoming = ::accept(static_cast<int>(sock), nullptr, nullptr);
+    auto const& sock = static_cast<Socket_type&>(
+        *static_cast<viua::types::Pointer*>(frame->arguments->get(0))->to(proc));
+    auto const incoming = ::accept(sock.fd(), nullptr, nullptr);
     if (incoming == -1) {
         auto const error_number = errno;
         auto const known_errors = std::map<decltype(error_number), std::string>{


@@ 731,7 742,7 @@ static auto accept(Frame* frame,
     frame->set_local_register_set(
         std::make_unique<viua::kernel::Register_set>(1));
     frame->local_register_set->set(
-        0, std::make_unique<viua::types::Integer>(incoming));
+        0, std::make_unique<Socket_type>(incoming, Socket_type::Ownership::Owned));
 }
 
 static auto write(Frame* frame,


@@ 739,12 750,11 @@ static auto write(Frame* frame,
                   viua::kernel::Register_set*,
                   viua::process::Process*,
                   viua::kernel::Kernel*) -> void {
-    auto const sock = static_cast<int>(
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer());
+    auto const& sock =
+        static_cast<Socket_type&>(*frame->arguments->get(0));
 
     auto const buffer  = frame->arguments->get(1)->str();
-    auto const written = ::write(sock, buffer.c_str(), buffer.size());
+    auto const written = ::write(sock.fd(), buffer.c_str(), buffer.size());
 
     if (written == -1) {
         auto const error_number = errno;


@@ 868,12 878,11 @@ static auto read(Frame* frame,
                  viua::kernel::Register_set*,
                  viua::process::Process*,
                  viua::kernel::Kernel*) -> void {
-    auto const sock = static_cast<int>(
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer());
+    auto const& sock =
+        static_cast<Socket_type&>(*frame->arguments->get(0));
 
     auto buffer        = std::array<char, 1024>{};
-    auto const n_bytes = ::read(sock, buffer.data(), buffer.size());
+    auto const n_bytes = ::read(sock.fd(), buffer.data(), buffer.size());
 
     if (n_bytes == 0) {
         throw std::make_unique<viua::types::Exception>("Eof",


@@ 1004,15 1013,14 @@ static auto recv(Frame* frame,
                  viua::kernel::Register_set*,
                  viua::process::Process*,
                  viua::kernel::Kernel*) -> void {
-    auto const sock = static_cast<int>(
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer());
+    auto const& sock =
+        static_cast<Socket_type&>(*frame->arguments->get(0));
     auto const buffer_length = static_cast<size_t>(
         static_cast<viua::types::Integer*>(frame->arguments->get(0))
             ->as_integer());
 
     auto buffer        = std::vector<char>(buffer_length, '\0');
-    auto const n_bytes = ::recv(sock, buffer.data(), buffer.size(), 0);
+    auto const n_bytes = ::recv(sock.fd(), buffer.data(), buffer.size(), 0);
 
     if (n_bytes == 0) {
         throw std::make_unique<viua::types::Exception>("Eof",


@@ 1152,11 1160,10 @@ static auto shutdown(Frame* frame,
                      viua::kernel::Register_set*,
                      viua::process::Process*,
                      viua::kernel::Kernel*) -> void {
-    auto const sock =
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer();
+    auto const& sock =
+        static_cast<Socket_type&>(*frame->arguments->get(0));
     // FIXME allow shutting down just SHUT_WR or SHUT_RD
-    if (::shutdown(static_cast<int>(sock), SHUT_RDWR) == -1) {
+    if (::shutdown(sock.fd(), SHUT_RDWR) == -1) {
         auto const error_number = errno;
         auto const known_errors = std::map<decltype(error_number), std::string>{
             {


@@ 1193,10 1200,9 @@ static auto close(Frame* frame,
                   viua::kernel::Register_set*,
                   viua::process::Process*,
                   viua::kernel::Kernel*) -> void {
-    auto const sock =
-        static_cast<viua::types::Integer*>(frame->arguments->get(0))
-            ->as_integer();
-    if (::close(static_cast<int>(sock)) == -1) {
+    auto const& sock =
+        static_cast<Socket_type&>(*frame->arguments->get(0));
+    if (::close(sock.fd()) == -1) {
         auto const error_number = errno;
         auto const known_errors = std::map<decltype(error_number), std::string>{
             {

M src/tooling/libs/lexer/normaliser.cpp => src/tooling/libs/lexer/normaliser.cpp +5 -0
@@ 1472,6 1472,11 @@ auto normalise(std::vector<Token> source) -> std::vector<Token> {
             case BITALTE:
             case BITAGT:
             case BITAGTE:
+            case IO_READ:
+            case IO_WRITE:
+            case IO_CLOSE:
+            case IO_WAIT:
+            case IO_CANCEL:
                 throw viua::tooling::errors::compile_time::Error_wrapper{}
                     .append(viua::tooling::errors::compile_time::Error{
                         viua::tooling::errors::compile_time::

M src/tooling/libs/parser/parse.cpp => src/tooling/libs/parser/parse.cpp +5 -0
@@ 1396,6 1396,11 @@ auto parse(std::vector<viua::tooling::libs::lexer::Token> const& tokens)
             case BITALTE:
             case BITAGT:
             case BITAGTE:
+            case IO_READ:
+            case IO_WRITE:
+            case IO_CLOSE:
+            case IO_WAIT:
+            case IO_CANCEL:
             default:
                 throw viua::tooling::errors::compile_time::Error_wrapper{}
                     .append(viua::tooling::errors::compile_time::Error{

M src/tooling/libs/static_analyser/static_analyser.cpp => src/tooling/libs/static_analyser/static_analyser.cpp +8 -0
@@ 3783,6 3783,14 @@ static auto analyse_single_arm(
 
                 break;
             }
+            case IO_READ:
+            case IO_WRITE:
+            case IO_CLOSE:
+            case IO_WAIT:
+            case IO_CANCEL: {
+                // FIXME add static analysis for I/O instructions
+                break;
+            }
             case RETURN:
             case HALT: {
                 /*

A src/types/io.cpp => src/types/io.cpp +271 -0
@@ 0,0 1,271 @@
+/*
+ *  Copyright (C) 2019 Marek Marecki
+ *
+ *  This file is part of Viua VM.
+ *
+ *  Viua VM is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Viua VM is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Viua VM.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/select.h>
+#include <unistd.h>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <viua/kernel/kernel.h>
+#include <viua/exceptions.h>
+#include <viua/types/integer.h>
+#include <viua/types/io.h>
+#include <viua/types/string.h>
+
+namespace viua::scheduler::io {
+auto IO_interaction::id() const -> id_type {
+    return assigned_id;
+}
+auto IO_interaction::cancel() -> void {
+    is_cancelled.store(true, std::memory_order_release);
+}
+auto IO_interaction::cancelled() const -> bool {
+    return is_cancelled.load(std::memory_order_acquire);
+}
+auto IO_interaction::abort() -> void {
+    is_aborted.store(true, std::memory_order_release);
+}
+auto IO_interaction::aborted() const -> bool {
+    return is_aborted.load(std::memory_order_acquire);
+}
+auto IO_interaction::complete() -> void {
+    is_complete.store(true, std::memory_order_release);
+}
+auto IO_interaction::completed() const -> bool {
+    return is_complete.load(std::memory_order_acquire);
+}
+
+IO_interaction::IO_interaction(id_type const x)
+    : assigned_id{x}
+{}
+IO_interaction::~IO_interaction() {}
+
+IO_interaction::Interaction_result::Interaction_result(
+    State const st
+    , Status const su
+    , result_type r)
+    : state{st}
+    , status{su}
+    , result{std::move(r)}
+{}
+
+auto IO_fake_interaction::interact() -> Interaction_result {
+    std::cerr << "IO_fake_interaction interacting!\n";
+    return Interaction_result{
+        IO_interaction::State::Complete,
+        IO_interaction::Status::Success,
+        std::make_unique<viua::types::String>("Hello, World!")
+    };
+}
+
+IO_read_interaction::IO_read_interaction(
+      id_type const x
+    , int const fd
+    , size_t const limit)
+    : IO_interaction(x)
+    , file_descriptor{fd}
+    , buffer(limit, '\0')
+{}
+auto IO_read_interaction::interact() -> Interaction_result {
+    if (cancelled()) {
+        return Interaction_result{
+            IO_interaction::State::Complete,
+            IO_interaction::Status::Cancelled,
+            std::make_unique<viua::types::Exception>("IO_cancel", "I/O cancelled")
+        };
+    }
+
+    auto const n = ::read(file_descriptor, buffer.data(), buffer.size());
+
+    if (n == -1) {
+        auto const saved_errno = errno;
+        return Interaction_result{
+            IO_interaction::State::Complete,
+            IO_interaction::Status::Error,
+            std::make_unique<viua::types::Integer>(saved_errno)
+        };
+    }
+    if (n == 0) {
+        return Interaction_result{
+            IO_interaction::State::Complete,
+            IO_interaction::Status::Success,
+            std::make_unique<viua::types::String>("")
+        };
+    }
+    buffer.resize(static_cast<decltype(buffer)::size_type>(n));
+    return Interaction_result{
+        IO_interaction::State::Complete,
+        IO_interaction::Status::Success,
+        std::make_unique<viua::types::String>(std::move(buffer))
+    };
+}
+
+IO_write_interaction::IO_write_interaction(
+      id_type const x
+    , int const fd
+    , std::string buf)
+    : IO_interaction(x)
+    , file_descriptor{fd}
+    , buffer{std::move(buf)}
+{}
+auto IO_write_interaction::interact() -> Interaction_result {
+    if (cancelled()) {
+        return Interaction_result{
+            IO_interaction::State::Complete,
+            IO_interaction::Status::Cancelled,
+            std::make_unique<viua::types::Exception>("IO_cancel", "I/O cancelled")
+        };
+    }
+
+    auto const n = ::write(file_descriptor, buffer.data(), buffer.size());
+
+    if (n == -1) {
+        auto const saved_errno = errno;
+        return Interaction_result{
+            IO_interaction::State::Complete,
+            IO_interaction::Status::Error,
+            std::make_unique<viua::types::Integer>(saved_errno)
+        };
+    }
+    return Interaction_result{
+        IO_interaction::State::Complete,
+        IO_interaction::Status::Success,
+        std::make_unique<viua::types::String>(buffer.substr(static_cast<size_t>(n)))
+    };
+}
+}
+
+namespace viua::types {
+std::string const viua::types::IO_port::type_name = "IO_port";
+
+std::string IO_port::type() const {
+    return "IO_port";
+}
+std::string IO_port::str() const {
+    return "";
+}
+std::string IO_port::repr() const {
+    return "";
+}
+bool IO_port::boolean() const {
+    return false;
+}
+
+std::unique_ptr<Value> IO_port::copy() const {
+    return std::unique_ptr<IO_port>();
+}
+
+IO_port::IO_port() {}
+IO_port::~IO_port() {}
+
+
+std::string const viua::types::IO_fd::type_name = "IO_fd";
+
+std::string IO_fd::type() const {
+    return "IO_fd";
+}
+std::string IO_fd::str() const {
+    return "IO_fd: " + std::to_string(file_descriptor);
+}
+std::string IO_fd::repr() const {
+    return std::to_string(file_descriptor);
+}
+bool IO_fd::boolean() const {
+    return true;
+}
+
+std::unique_ptr<Value> IO_fd::copy() const {
+    throw std::make_unique<viua::types::Exception>("Not_copyable");
+}
+
+auto IO_fd::fd() const -> int {
+    return file_descriptor;
+}
+auto IO_fd::read(viua::kernel::Kernel& k, std::unique_ptr<Value> x) -> std::unique_ptr<IO_request> {
+    using viua::scheduler::io::IO_interaction;
+    using viua::scheduler::io::IO_read_interaction;
+
+    auto const interaction_id = IO_interaction::id_type{ file_descriptor, counter++ };
+    auto const limit = static_cast<viua::types::Integer*>(x.get())->as_integer();
+    k.schedule_io(std::make_unique<IO_read_interaction>(
+          interaction_id
+        , file_descriptor
+        , static_cast<size_t>(limit)
+    ));
+    return std::make_unique<IO_request>(&k, interaction_id);
+}
+auto IO_fd::write(viua::kernel::Kernel& k, std::unique_ptr<Value> x) -> std::unique_ptr<IO_request> {
+    using viua::scheduler::io::IO_interaction;
+    using viua::scheduler::io::IO_write_interaction;
+
+    auto const interaction_id = IO_interaction::id_type{ file_descriptor, counter++ };
+    auto buffer = x->str();
+    k.schedule_io(std::make_unique<IO_write_interaction>(
+          interaction_id
+        , file_descriptor
+        , std::move(buffer)
+    ));
+    return std::make_unique<IO_request>(&k, interaction_id);
+}
+auto IO_fd::close() -> void {
+    if (ownership == Ownership::Owned) {
+        ::close(file_descriptor);
+    }
+}
+
+IO_fd::IO_fd(int const x, Ownership const o)
+    : file_descriptor{x}
+    , ownership{o}
+{}
+IO_fd::~IO_fd() {
+    close();
+}
+
+
+std::string const viua::types::IO_request::type_name = "IO_request";
+
+std::string IO_request::type() const {
+    return "IO_request";
+}
+std::string IO_request::str() const {
+    auto const [ a, b ] = interaction_id;
+    return "<I/O request: " + std::to_string(a) + "." + std::to_string(b) + ">";
+}
+std::string IO_request::repr() const {
+    return "<I/O request>";
+}
+bool IO_request::boolean() const {
+    return true;
+}
+
+std::unique_ptr<Value> IO_request::copy() const {
+    throw std::make_unique<viua::types::Exception>(
+          "IO_request"
+        , "not copyable"
+    );
+}
+
+IO_request::IO_request(viua::kernel::Kernel* k, interaction_id_type const x)
+    : interaction_id{x}
+    , kernel{k}
+{}
+IO_request::~IO_request() {
+    kernel->cancel_io(id());
+}
+}

M tests/tests.py => tests/tests.py +1 -1
@@ 1311,7 1311,7 @@ class VectorInstructionsTests(unittest.TestCase):
     PATH = './sample/asm/vector'
 
     def testPackingVec(self):
-        runTest(self, 'vec_packing.asm', '["answer to life", 42]', 0, lambda o: o.strip())
+        runTest(self, 'vec_packing.asm', '[b"answer to life", 42]', 0, lambda o: o.strip())
 
     def testPackingVecRefusesToPackItself(self):
         # pass --no-sa because we want to test runtime exception