~maelkum/viuavm

3402ca346f8818dea80e06b735748081327a0fe3 — Marek Marecki 1 year, 4 days 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