Commit e23a10ea authored by Kunshan Wang's avatar Kunshan Wang

Multiple return values.

parent 1bfaf6ab
......@@ -64,7 +64,7 @@ present, they mean that the common instruction accepts exception clause or
keepalive clause, respectively. ``RTs`` are the return types. If the type
parameter list or the value parameter list are omitted, it means the common
instruction takes no type parameters or value parameters, respectively. If the
return type is omitted, it returns ``void``.
return type is omitted, it returns no results (equivalent to ``-> ()``).
The names of many common instructions are grouped by prefixes, such as
``@uvm.tr64.``. In this document, their common prefixes may be omitted in their
......@@ -271,7 +271,7 @@ Object pinning
::
[0x240]@uvm.native.pin <T> (%opnd: T) -> uptr<U>
[0x241]@uvm.native.unpin <T> (%opnd: T) -> void
[0x241]@uvm.native.unpin <T> (%opnd: T)
*T* must be ``ref<U>`` or ``iref<U>`` for some U.
......@@ -306,7 +306,7 @@ convention *callConv* with cookie *cookie*.
::
[0x243]@uvm.native.unexpose [callconv] (%value: U) -> void
[0x243]@uvm.native.unexpose [callconv] (%value: U)
*callconv* is a platform-specific calling convention flag. *U* is determined by
the calling convention.
......@@ -335,7 +335,7 @@ line::
.typedef @uvm.meta.refs = hybrid <int<64> ref<void>> // ID: 0x262
.typedef @uvm.meta.refs.r = ref<@uvm.meta.refs.r> // ID: 0x263
.funcsig @uvm.meta.trap_handler.sig = void (stackref int<32> ref<void>) // ID: 0x264
.funcsig @uvm.meta.trap_handler.sig = (stackref int<32> ref<void>) -> () // ID: 0x264
In ``bytes`` and ``refs``, the fixed part is the length of the variable part.
``bytes`` represents a byte array. ASCII strings are also represented this way.
......@@ -407,16 +407,14 @@ On-stack replacement
::
[0x258]@uvm.meta.pop_frame (%stack: stackref)
[0x259]@uvm.meta.push_frame <[sig]> (%stack: stackref, %func: funcref<sig>, ...)
[0x259]@uvm.meta.push_frame <[sig]> (%stack: stackref, %func: funcref<sig>)
``%stack`` and ``%func`` must not be ``NULL``.
- ``pop_frame`` pops the top frame of the stack ``%stack``.
- ``push_frame`` creates a new frame on top of the stack ``%stack`` for the Mu
function ``%func``. ``%func`` must have the signature ``sig``. There are many
arguments after ``%func``. Each will be an argument to ``%func``. Their types
must match the argument types of ``%func``.
function ``%func``. ``%func`` must have the signature ``sig``.
The return type of ``%func`` must match the expected return type of the
previous frame. If the previous frame is a native frame, the new function must
......@@ -452,9 +450,8 @@ A trap handler takes three parameters:
2. The watchpoint ID, or 0 if triggered by the ``TRAP`` instruction.
3. The user data, which is provided when registering.
A trap handler is run by the same Mu thread that caused the trap and is
guaranteed to run on a different stack from the one that caused the trap. The
stack that runs the trap handler is allowed to be killed.
A trap handler is run by the same Mu thread that caused the trap and is executed
on a new stack.
A trap handler *usually* terminates by either executing the ``@uvm.thread_exit``
instruction (probably also kill the old stack before exiting), or ``SWAPSTACK``
......
......@@ -2550,11 +2550,11 @@ The return values of ``CCALL`` are the return values of the native callee.
``CCALL`` cannot receive exceptions thrown by C++.
``CCALL`` is an OSR point. A stack may enter the ``READY<T>`` state with some Mu
frames stopping on ``CCALL`` instructions. If the top frame is stopping at this
instruction, the value passed to the stack when rebinding becomes the return
value of this instruction. However, whether a native frame can be popped is
implementation-defined.
``CCALL`` is an OSR point. A stack may enter the ``READY<Rs>`` state, where Rs
are the return types of the callee, with some Mu frames stopping on ``CCALL``
instructions. If the top frame is stopping at this instruction, the value passed
to the stack when rebinding becomes the return value of this instruction.
However, whether a native frame can be popped is implementation-defined.
For LLVM users: Mu is not designed to be compatible with C and functions
defined in Mu IR does not use the native C ABI. This instruction is
......@@ -2959,8 +2959,8 @@ See `<common-insts.rest>`__ for a complete list of common instructions.
// Handle signed integer overflow here.
// extra stack/thread operations
%s = NEWSTACK <...> @foo (...)
%t = COMMINST @uvm.new_thread (%s)
%s = COMMINST @uvm.new_stack <[@sig]> (@func)
%t = NEWTHREAD %s THROW_EXC %some_exc
// the proper thread exit
COMMINST @uvm.thread_exit // no type args or value args
......
......@@ -347,7 +347,6 @@ a dependency from* A, if:
- the data value of A is used as a data argument of B unless:
* the value of A has type void, or
* A is used in the ``KEEPALIVE`` clause of B, or
* B is a ``SELECT`` instruction and A is its ``cond`` argument or a is the
``iftrue`` or ``iffalse`` argument not selected by ``cond``, or
......
......@@ -18,7 +18,6 @@ typedef void *MuIRefValue; // iref<T>
typedef void *MuStructValue; // struct<...>
typedef void *MuArrayValue; // array<T l>
typedef void *MuVectorValue; // vector<T l>
typedef void *MuVoidValue; // void
typedef void *MuFuncRefValue; // funcref<sig>
typedef void *MuThreadRefValue; // threadref
typedef void *MuStackRefValue; // stackref
......@@ -36,9 +35,11 @@ typedef void (*MuCFP)();
// Result of a trap handler
typedef int MuTrapHandlerResult;
// Used by new_thread
typedef int MuHowToResume;
#define MU_THREAD_EXIT 0x00
#define MU_REBIND_PASS_VALUE 0x01
#define MU_REBIND_PASS_VALUES 0x01
#define MU_REBIND_THROW_EXC 0x02
// Declare the types here because they are used in the following signatures.
......@@ -48,7 +49,8 @@ typedef struct MuCtx MuCtx;
// Signature of the trap handler
typedef void (*MuTrapHandler)(MuCtx *ctx, MuThreadRefValue thread,
MuStackRefValue stack, int wpid, MuTrapHandlerResult *result,
MuStackRefValue *new_stack, MuValue *value, MuRefValue *exception,
MuStackRefValue *new_stack, MuValue *values, int nvalues,
MuRefValue *exception,
MuCPtr userdata);
// Memory orders
......@@ -202,8 +204,9 @@ struct MuCtx {
void (*fence )(MuCtx *ctx, MuMemOrd ord);
// Thread and stack creation and stack destruction
MuStackRefValue (*new_stack )(MuCtx *ctx, MuFuncRefValue func, MuValue *args, int nargs);
MuThreadRefValue (*new_thread)(MuCtx *ctx, MuStackRefValue stack);
MuStackRefValue (*new_stack )(MuCtx *ctx, MuFuncRefValue func);
MuThreadRefValue (*new_thread)(MuCtx *ctx, MuStackRefValue stack,
MuHowToResume *htr, MuValue *vals, int nvals, MuRefValue *exc);
void (*kill_stack)(MuCtx *ctx, MuStackRefValue stack);
// Stack introspection
......
......@@ -86,10 +86,11 @@ The Default Calling Convention
The *default* calling convention, denoted by the ``#DEFAULT`` flag in the IR,
follows the AMD64 ABI in register usage, stack frame structure, parameter
passing and returning. The parameter types and the return types are mapped to C
types according to the above table. Mu ``struct`` and ``array`` types are mapped
to C structs of corresponding members. ``array`` cannot be the type of
parameters or return values because C disallows this, but arrays within structs
and pointers to arrays are is allowed.
types according to the above table. As a special case, if the native function
returns void, the corresponding Mu signature returns no values ``(...) -> ()``.
Mu ``struct`` and ``array`` types are mapped to C structs of corresponding
members. ``array`` cannot be the type of parameters or return values because C
disallows this, but arrays within structs and pointers to arrays are is allowed.
Arguments and return values are passed in registers and the memory according to
the AMD64 ABI, with the types of Mu arguments and the return type mapped to the
......
......@@ -367,8 +367,8 @@ The ``pop_frame`` function has implementation-defined behaviours when popping
native frames.
During OSR, if the top frame is a native frame, the state of the stack is
``READY<T>`` where *T* is the return type of the previous frame on top of the
native frame.
``READY<Ts>`` where *Ts* are the return types of the previous frame on top of
the native frame.
When rebinding a thread to a stack with a value, and the top frame is on a call
site (native or Mu), the value associated with the rebinding is the return value
......
......@@ -86,45 +86,41 @@ Here is a summary of the state and transition of stacks.
======================= =============================== =======================
Operation Current Stack New/Destination Stack
======================= =============================== =======================
create new stack N/A READY<void>
create new thread N/A READY<void> -> ACTIVE
swap-stack ACTIVE -> READY<Ts> or DEAD READY<Us> -> ACTIVE
@uvm.kill_stack N/A READY<Ts> -> DEAD
create new stack N/A READY<Ts>
create new thread N/A READY<Ts> -> ACTIVE
SWAPSTACK ACTIVE -> READY<Ts> or DEAD READY<Us> -> ACTIVE
killing a stack N/A READY<Ts> -> DEAD
@uvm.thread_exit ACTIVE -> DEAD N/A
trap to client ACTIVE -> READY<Ts> N/A
undefined function ACTIVE N/A
stack-related errors ACTIVE N/A
popping a frame READY<Ts> -> READY<Us> N/A
pushing a frame READY<Ts> -> READY<Us> N/A
======================= =============================== =======================
TODO: https://github.com/microvm/microvm-meta/issues/42 Executing undefined
functions would result in traps, too.
Stack and Thread Creation
=========================
Mu stacks and Mu threads can be created by Mu instructions ``NEWSTACK`` and
``@uvm.new_thread``, or the API function ``new_stack`` and ``new_thread``.
Mu stacks and Mu threads can be created by Mu instructions ``@uvm.new_stack`` and
``NEWTHREAD``, or the API function ``new_stack`` and ``new_thread``.
When a stack is created, a Mu function and all of its parameter must be
provided. The stack will contain a frame created for the current version of the
function (as seen by the current thread because of concurrency and the memory
model). This frame is called the **stack-bottom frame** and the function is
called the **stack-bottom function**.
When a stack is created, a Mu function must be provided. The stack will contain
a frame created for the current version of the function (as seen by the current
thread because of concurrency and the memory model). This frame is called the
**stack-bottom frame** and the function is called the **stack-bottom function**.
NOTE: The stack-bottom frame is conceptually the last frame in a Mu stack
and returning from that frame has undefined behaviour. But a concrete Mu
implementation can still have its own frames or useful data below the
stack-bottom frame. They are implementation-specific details.
A newly created stack is in the **READY<void>** state. When bound (explained
later) and passed the ``void`` value (``NULL``), it continues from the first
instruction of its stack-bottom function.
A newly created stack is in the **READY<Ts>** state, where Ts are the parameter
types of the stack-bottom function. When bound (explained later), it continues
from the beginning of its stack-bottom function.
When a thread is created, a stack must be provided as its **initial stack**. The
initial stack must be in the **READY<void>** state. The new thread binds
(explained later) to the initial stack, passing the ``void`` value (``NULL``) to
it, thus the stack will enter the **ACTIVE** state after the thread is created.
A newly created thread can execute immediately.
When a thread is created, a stack must be provided as its **initial stack**.
Creating a thread binds the thread to the stack, passing values or raising
exception to it (explained later), thus the stack will enter the **ACTIVE**
state after the thread is created. A newly created thread starts execution
immediately.
NOTE: Unlike Java, there is not a separate step to "start" a thread. A
thread starts when it is created.
......@@ -147,20 +143,19 @@ Binding of Stack and Thread
Binding
-------
Some actions, including the ``@uvm.new_thread`` and the ``SWAPSTACK``
instruction, the ``new_thread`` API function and the trap handler, can bind a
thread to a stack.
Some actions, including the ``NEWTHREAD`` and the ``SWAPSTACK`` instruction, the
``new_thread`` API function and the trap handler, can bind a thread to a stack.
When **binding** a thread to a stack, the state of the stack changes from
**READY<T>** to **ACTIVE**. In this process, one of the following two actions
**READY<Ts>** to **ACTIVE**. In this process, one of the following two actions
shall be performed on the stack:
- A binding operation can **pass a value** of type *T* to the stack. In this
case, the stack must be in the **READY<T>** state, and it **receives the
value**. *T* can be ``void``.
- A binding operation can **pass values** of types *Ts* to the stack. In this
case, the stack must be in the **READY<Ts>** state, and it **receives the
values**. *Ts* can be an empty list.
- A binding operation can **raise an exception** to the stack. In this case, the
stack can be in **READY<T>** with any *T* and it **receives the exception**.
stack can be in **READY<Ts>** with any *Ts* and it **receives the exception**.
It gives undefined behaviour if the stack is not in the expected state.
......@@ -173,8 +168,8 @@ the ``SWAPSTACK`` instruction, can unbind a thread from a stack.
When **unbinding** a thread from a stack, one of the following two actions shall
be performed on the stack:
An unbinding operation can **leave the stack** with a return type *T*. In this
case, the state of the stack changes from **ACTIVE** to **READY<T>**.
An unbinding operation can **leave the stack** with a return types *Ts*. In this
case, the state of the stack changes from **ACTIVE** to **READY<Ts>**.
An unbinding operation can **kill the stack**. In this case, the state of the
stack changes from **ACTIVE** to **DEAD**. Specifically the ``@uvm.thread_exit``
......@@ -182,7 +177,7 @@ kills the current stack and the ``SWAPSTACK`` instruction can do either option
on the swapper.
Executing a ``TRAP`` or an enabled ``WATCHPOINT`` instruction implies an
unbinding operation, leaving the current stack to a **READY<T>** state.
unbinding operation, leaving the current stack to a **READY<Ts>** state.
Swap-stack
----------
......@@ -221,15 +216,16 @@ current thread) of a given function and the given arguments.
After popping a frame from a stack, the next stack below the top becomes the new
top frame. At this moment, either the new frame is a native frame or it is a Mu
frame pausing on an OSR point instruction. In either case, the stack now enters
the ``READY<T>`` state where *T* (may be ``void``) is the return type of the
the ``READY<Ts>`` state where *Ts* (may be ``void``) is the return type of the
frame just popped. The value passed to such a stack will be received by the
native caller, or the OSR-point instruction. If an exception is thrown, it is
thrown into the native frame, which has implementation-defined behaviours, or to
the Mu frame, where the OSR-point instruction can catch the exception.
After pushing a frame to a stack, it enters the ``READY<void>`` state and will
continue from the beginning of the function of the frame. Throwing an exception
to such a stack will throw the exception out of the top frame.
After pushing a frame to a stack, it enters the ``READY<Ts>`` state, where Ts is
the argument types of the new function, and will continue from the beginning of
the function of the frame. Throwing an exception to such a stack will throw the
exception out of the top frame.
NOTE: For the ease of Mu implementation, the new function must continue
from the beginning rather than an arbitrary instruction in the middle.
......@@ -248,6 +244,10 @@ to such a stack will throw the exception out of the top frame.
continue from the appropriate bytecode instruction. The optimising compiler
can handle the rest.
TODO: With the goto-with-values form defined, we can extend the IR and the
API so that execution can continue from an arbitrary basic block of an
arbitrary function version, rather than just the beginning of a function.
Futex
=====
......
......@@ -109,7 +109,6 @@ opaque **handles**. Those handles are defined as::
typedef void *MuIRefValue; // iref<T>
typedef void *MuStructValue; // struct<...>
typedef void *MuArrayValue; // array<T l>
typedef void *MuVoidValue; // void
typedef void *MuFuncRefValue; // funcref<sig>
typedef void *MuThreadRefValue; // threadref
typedef void *MuStackRefValue; // stackref
......@@ -669,33 +668,38 @@ Stack and thread operations
::
MuStackRefValue (*new_stack )(MuCtx *ctx, MuFuncRefValue func);
MuThreadRefValue (*new_thread)(MuCtx *ctx, MuStackRefValue stack, MuValue *vals, int nvals);
MuThreadRefValue (*new_thread)(MuCtx *ctx, MuStackRefValue stack,
MuHowToResume *htr, MuValue *vals, int nvals, MuRefValue *exc);
void (*kill_stack)(MuCtx *ctx, MuStackRefValue stack);
typedef int MuHowToResume;
#define MU_REBIND_PASS_VALUES 0x01
#define MU_REBIND_THROW_EXC 0x02
``new_stack`` creates a new Mu stack with ``func`` as the bottom function.
Returns a stackref to the new stack. The new stack stops in the very beginning
of ``func``, and its state is ``READY<Ts>``, where *Ts* is the parameter types
of ``func``.
``new_thread`` creates a new Mu thread and binds it to ``stack``. ``stack`` must
be in the ``READY<Ts>`` state for some types *Ts*. Arguments are provided in the
``args`` array. ``nargs`` is the number of arguments. The type of arguments
must match the signature of ``func``.Returns a threadref to the new thread.
be in the ``READY<Ts>`` state for some types *Ts*. ``*htr`` determines how to
bind to the stack. Possible values are ``MU_REBIND_PASS_VALUES`` for passing
values and ``MU_REBIND_THROW_EXC`` for throwing an exception. Values are
provided by the ``args`` array. ``nargs`` is the number of values. The types of
values must match *Ts*. The exception is provided by ``*exc``. Returns a
threadref to the new thread.
``kill_stack`` kills the ``stack``. The stack must be in the ``READY<T>`` state,
where *T* can be void. The stack enters the ``DEAD`` state. If the stack
contains native frames, the behaviour is implementation-defined.
``kill_stack`` kills the ``stack``. The stack must be in the ``READY<Ts>`` state
for some *Ts*. The stack enters the ``DEAD`` state. If the stack contains
native frames, the behaviour is implementation-defined.
``func`` and ``stack`` must not be ``NULL``.
For Lua users: ``new_stack`` is similar to creating a coroutine by creating
a new ``lua_State`` and pushing the function and its arguments.
``new_thread`` is like resuming a coroutine, but on a different stack, and
Mu supplies the arguments when creating the stack rather than creating the
thread. The real counterpart of ``lua_resume`` is the ``SWAPSTACK``
instruction, which is not available in the API. ``kill_stack`` is similar to
``lua_close``.
For Lua users: ``new_stack`` is similar to ``coroutine.create``.
``new_thread`` is similar to ``coroutine.resume``, but actually creates a
new thread. The real counterpart of ``lua_resume`` is the ``SWAPSTACK``
instruction, which is in the Mu IR but not available in the API.
``kill_stack`` is similar to ``lua_close``.
For JVM users: The JVM does not distinguish stacks and threads. The closest
counterpart is the JVM TI function ``StopThread``, but the Mu ``kill_stack``
......@@ -756,16 +760,15 @@ On-stack replacement
::
void (*pop_frame )(MuCtx *ctx, MuStackRefValue stack);
void (*push_frame)(MuCtx *ctx, MuStackRefValue stack, MuFuncRefValue func, MuValue *args, int nargs);
void (*push_frame)(MuCtx *ctx, MuStackRefValue stack, MuFuncRefValue func);
* ``pop_frame`` pops the top frame of the stack. Popping native frames has
implementation-defined behaviour. Popping a Mu frame changes the state of the
stack to ``READY<T>`` where T is the return type of the function of the popped
frame.
stack to ``READY<Ts>`` where Ts are the return types of the function of the
popped frame.
* ``push_frame`` creates a new frame on the top of ``stack`` for function
``func``, using the values in the ``args`` array as its arguments. ``nargs``
is the number of arguments. Arguments must match the signature of ``func``.
``func``.
If the previous stack top frame is a Mu frame, it must be pausing on the
``CALL`` instruction. The return type of ``func`` must be the same as return
......@@ -774,9 +777,10 @@ On-stack replacement
If the previous stack top frame is a native frame, the new function must
have the same return type as the previous Mu callee.
After ``push_frame``, the stack enters the ``READY<void>`` state. When the
stack is rebound, it continues from the beginning of ``func``. The return
value or exceptions from ``func`` are received by the caller.
After ``push_frame``, the stack enters the ``READY<Ts>`` state where Ts are
the argument types of ``func``. When the stack is rebound, it continues from
the beginning of ``func``. The return value or exceptions from ``func`` are
received by the caller.
..
......@@ -920,7 +924,8 @@ The signature of trap handlers is::
typedef void (*MuTrapHandler)(MuCtx *ctx, MuThreadRefValue thread,
MuStackRefValue stack, int wpid, MuTrapHandlerResult *result,
MuStackRefValue *new_stack, MuValue *value, MuRefValue *exception,
MuStackRefValue *new_stack, MuValue *values, int nvalues,
MuRefValue *exception,
MuCPtr userdata);
where::
......@@ -928,7 +933,7 @@ where::
typedef int MuTrapHandlerResult;
#define MU_THREAD_EXIT 0x00
#define MU_REBIND_PASS_VALUE 0x01
#define MU_REBIND_PASS_VALUES 0x01
#define MU_REBIND_THROW_EXC 0x02
``ctx`` is a new client context created for this particular trap event.
......@@ -944,19 +949,13 @@ close it manually.
Before returning, the trap handler should set ``*result``:
* ``MU_THREAD_EXIT``: The thread ``thread`` terminates.
* ``Mu_REBIND_PASS_VALUE``: The thread ``thread`` will be rebound to a stack
``*new_stack``. The value ``*value`` is passed to ``*new_stack``. The type of
``*value`` must match the type expected by ``*new_stack``.
* ``Mu_REBIND_PASS_VALUES``: The thread ``thread`` will be rebound to a stack
``*new_stack``. The values ``*values`` are passed to ``*new_stack``. The types
of ``*values`` must match the type expected by ``*new_stack``. ``nvalues`` is
the number of elements in the ``*values`` array.
* ``Mu_REBIND_THROW_EXC``: The thread ``thread`` will be rebound to a stack
``*new_stack``. It throws exception ``*exception`` to the stack.
..
NOTE: More often than not, it is desirable to pass the ``void`` value:
``NULL``. The client should have pre-defined a Mu ``void`` constant, such
as: ``.const @VOID <@void> = NULL``. Then this constant can be obtained by
``ctx->handle_from_const``.
In all cases, if ``*new_stack``, ``*value`` and/or ``*exception`` are used, they
must be set and must be held by ``ctx``.
......
......@@ -187,7 +187,7 @@ The global names are inferred from their syntactic parents:
Example::
.funcsig @fac.sig = ... (... ...)
.funcsig @fac.sig = (... ...) -> (...)
.funcdef @fac VERSION %v1 <@fac.sig> {
%entry(<@i32> %n):
......
......@@ -189,7 +189,8 @@ value of the strong variant of type *T*.
A **store** operation has parameters ``(ord, T, loc, newVal)``. *ord is the
memory order of the operation. *T* is the type of *loc*, a memory location.
*newVal* is a value whose type is the strong variant of *T*. The result is void.
*newVal* is a value whose type is the strong variant of *T*. This operation does
not produce values as result.
A **compare exchange** operation is an atomic read-modify-write operation. Its
parameters are ``(isWeak, ordSucc, ordFail, T, loc, expected, desired)``.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment