Commit ebdcf858 authored by Kunshan Wang's avatar Kunshan Wang

Removed the undefine function handler.

parent 9fd4cac2
......@@ -298,7 +298,6 @@ line::
.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.undef_func_handler.sig = void (int<32> ref<void>) // ID: 0x265
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.
......@@ -332,19 +331,32 @@ determines whether it is binary or text.
Stack introspection
-------------------
- ``[0x254]@uvm.meta.cur_func_Ver (%stack: stackref, %frame: int<32>) -> int<32>``
- ``[0x255]@uvm.meta.cur_inst (%stack: stackref, %frame: int<32>) -> int<32>``
- ``[0x256]@uvm.meta.dump_keepalives (%stack: stackref, %frame: int<32>) -> @uvm.meta.refs.r``
- ``[0x254]@uvm.meta.cur_func (%stack: stackref, %frame: int<32>) -> int<32>``
- ``[0x255]@uvm.meta.cur_func_Ver (%stack: stackref, %frame: int<32>) -> int<32>``
- ``[0x256]@uvm.meta.cur_inst (%stack: stackref, %frame: int<32>) -> int<32>``
- ``[0x257]@uvm.meta.dump_keepalives (%stack: stackref, %frame: int<32>) -> @uvm.meta.refs.r``
``cur_func_ver`` returns the ID of the frame. Returns 0 for native frames.
..
``cur_inst`` returns the ID of the current instruction of the frame. Returns 0
if the frame is just created or the frame is native.
``dump_keepalives`` returns an list of references. There are as many elements as
there are keep-alive variables. Each reference has type ``ref<T>`` where T is
the type of the corresponding keep-alive variable. The returned list and the
objects pointed to must not be modified. Must not be used on native frames.
* ``cur_func`` returns the ID of the current function of ``%frame``
in ``%stack``. Returns 0 if the frame is native.
* ``cur_func_ver`` returns the ID of the current function version of ``%frame``
in ``%stack``. Returns 0 if the frame is native, or the function of the frame
is undefined.
* ``cur_inst`` returns the ID of the current instruction of ``%frame`` in
``%stack``. Returns 0 if the frame is just created, its function is undefined,
or the frame is native.
* ``dump_keepalives`` dumps the values of the keep-alive variables of the
current instruction in the selected frame. If the function is undefined, the
arguments are the keep-alive variables. Cannot be used on native frames. The
return value is a list of object references, each of which refers to an object
which has type *T* and contains value *v*, where *T* and *v* are the type and
the value of the corresponding keep-alive variable, respectively.
``%stack`` and ``%frame`` selects a frame in the stack ``%stack``. 0 is the top,
1 is the next top, ... ``%stack`` must not be null.
......@@ -352,8 +364,8 @@ objects pointed to must not be modified. Must not be used on native frames.
On-stack replacement
--------------------
- ``[0x257]@uvm.meta.pop_frame (%stack: stackref)``
- ``[0x258]@uvm.meta.push_frame <[sig]> (%stack: stackref, %func: funcref<sig>, ...)``
- ``[0x258]@uvm.meta.pop_frame (%stack: stackref)``
- ``[0x259]@uvm.meta.push_frame <[sig]> (%stack: stackref, %func: funcref<sig>, ...)``
``pop_frame`` pops the top frame of the stack ``%stack``.
......@@ -371,21 +383,22 @@ same return type as the previous Mu callee.
Watchpoint operations
---------------------
- ``[0x259]@uvm.meta.enable_watchpoint (%wpid: int<32>)``
- ``[0x25a]@uvm.meta.disable_watchpoint (%wpid: int<32>)``
- ``[0x25a]@uvm.meta.enable_watchpoint (%wpid: int<32>)``
- ``[0x25b]@uvm.meta.disable_watchpoint (%wpid: int<32>)``
``enable_watchpoint`` enables all watchpoints of watchpoint ID ``%wpid``.
``disenable_watchpoint`` disables all watchpoints of watchpoint ID ``%wpid``.
Trap and undefined function handling
------------------------------------
Trap handling
-------------
- ``[0x25c]@uvm.meta.set_trap_handler (%handler: funcref<@uvm.meta.trap_handler.sig>, %userdata: ref<void>)``
- ``[0x25b]@uvm.meta.set_trap_handler (%handler: funcref<@uvm.meta.trap_handler.sig>, %userdata: ref<void>)``
- ``[0x25c]@uvm.meta.set_undef_func_handler (%handler: funcref<@uvm.meta.undef_func_handler.sig>, %userdata: ref<void>)``
This instruction registers a trap handler. ``%handler`` is the function to be
called and ``%userdata`` will be their last argument when called.
These instructions register trap handlers and undefined function handlers,
respectively. ``%handler`` is the function to be called and ``%userdata`` will
be their last argument when called.
This instruction overrides the trap handler registered via the C-based client
API.
A trap handler takes three parameters:
......@@ -394,23 +407,13 @@ A trap handler takes three parameters:
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.
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 *usually* terminates by either executing the ``@uvm.thread_exit``
instruction (probably also kill the old stack before exiting), or ``SWAPSTACK``
back to another stack while killing the stack the trap handler was running on.
An undefined function handler takes two arguments:
1. The ID of the function.
2. The user data.
The undefined function handler runs on a different stack by a different thread
than that caused this event.
When the undefined function returns, the old function call or the ``NEWSTACK``
instruction is tried again.
Notes about dynamism
--------------------
......
......@@ -1035,7 +1035,7 @@ If *cond* is 1, jump to *ifTrue*, otherwise jump to *ifFalse*.
``SWITCH`` ``<`` *T* ``>`` *opnd* *default* ``{`` *(* *value* ``:`` *dest* ``;`` *)* :sub:`rep` ``}``
T
*type*: The type of *opnd* and *value*s
*type*: The type of *opnd* and all *value*
opnd
*variable* of *T*: The value to compare against.
default:
......@@ -1265,12 +1265,9 @@ have any exception clause and is not an OSR point.
NOTE: ``TAILCALL`` is semantically similar to calling a function and
immediately return the returned value, but reuses the current frame.
If the callee is not defined, the client will handle this by defining the
function. After returning from the client, this instruction will be tried again.
TODO: https://github.com/microvm/microvm-meta/issues/42 After this change,
an undefined function will invoke a trap by itself, and the caller does not
need to know it. So the paragraph above can be removed.
Calling an undefined function is allowed. Such functions will trigger a trap so
that the client can handle it. See `Mu IR <uvm-ir>`__ for the behaviour of
undefined functions.
..
......@@ -2718,12 +2715,19 @@ function and *argList* as its arguments.
This instruction continues exceptionally if Mu cannot allocate the new stack.
If the callee is not defined, the client will handle this by defining the
function. After returning from the client, this instruction will be tried again.
Which function version is used for the stack-bottom frame is determined by when
the stack is created, not when the stack is resumed. See `Memory Model
<memory-model>`__.
NOTE: This means it may still run an old version if a newer version is
loaded by the client between when the stack is created and when the stack is
resumed.
TODO: Is this the most "implement-able" and most desirable way?
TODO: https://github.com/microvm/microvm-meta/issues/42 Change is proposed
to let the callee trigger the trap when executed. So the paragraph above can
be removed.
Creating a stack for an undefined function is allowed. It will trap when that
frame is executed. See `Mu IR <uvm-ir>`__ for the behaviour of undefined
functions.
..
......
......@@ -228,20 +228,19 @@ All evaluations performed by a Mu thread form a total order, in which the
operations performed by each evaluation are **sequenced before** operations
performed by its successor.
All operations performed by a Mu client via a particular client agent form a
total order, in which each operation is **sequenced before** its successor.
All operations performed by a Mu client via a particular client context of the
API form a total order, in which each operation is **sequenced before** its
successor.
Specifically, operations performed by trap handlers and undefined function
handlers in the client are ordered with other operations performed by the Mu
thread as if they are performed by the ``TRAP``, ``WATCHPOINT``, ``CALL``,
``NEWSTACK`` or other instructions that transfers the control to the handler.
Operations before a ``TRAP`` or ``WATCHPOINT`` are **sequenced before** the
operations in the trap handler. Operations in the trap handler are **sequenced
before** operations after that ``TRAP`` or ``WATCHPOINT``.
The **program order** contains operations and their "sequenced before"
relations.
NOTE: This means all Mu instructions plus all client operations done by
the trap handler and undefined function handler in a Mu thread still forms
a total order.
NOTE: This means all Mu instructions plus all client operations done by the
trap handler in a Mu thread still forms a total order.
In C, the program order is a partial order even in a single thread because
of unspecified order of evaluations.
......@@ -562,19 +561,34 @@ Special Rules for Functions and Function Redefinition
The rules of memory access applies to functions as if
* a function were a memory location, and
* a function were a memory location that holds a function version, and
* a function call or a creation of new stack were an atomic load on that
location of the RELAXED order, and
* a creation of a frame for a function were an atomic load on that location of
the RELAXED order, which sees a particular version, and
* a function definition or redefinition during the load of a bundle were an
atomic store on that location of the RELAXED order.
atomic store on that location of the RELAXED order, which stores a new
version.
..
NOTE: A frame is created when:
1. calling a function by the ``CALL`` or ``TAILCALL`` instructions, or by
native programs through exposed Mu functions, or
2. creating a new stack by the ``NEWSTACK`` instruction or the ``new_stack``
API, or
3. pushing a new frame by the ``push_frame`` API or the
``@uvm.meta.push_frame`` instruction.
The order of definitions and redefinitions of a particular function is
consistent with the order the bundles that contain the definitions are loaded.
NOTE: This means fences must be inserted to guarantee other threads other
than the one which loads a bundle see a more recent version of a function.
NOTE: This means synchronisation operations must be used to guarantee other
threads other than the one which loads a bundle see the most recent version
of a function.
Out-of-thin-air or Speculative stores
=====================================
......
......@@ -51,9 +51,6 @@ typedef void (*MuTrapHandler)(MuCtx *ctx, MuThreadRefValue thread,
MuStackRefValue *new_stack, MuValue *value, MuRefValue *exception,
MuCPtr userdata);
// Signature of the undefined funciton handler
typedef void (*MuUndefFuncHandler)(MuCtx *ctx, MuID func_id, MuCPtr userdata);
// Memory orders
typedef int MuMemOrd;
......@@ -105,7 +102,6 @@ struct MuVM {
// Set handlers
void (*set_trap_handler )(MuVM *mvm, MuTrapHandler trap_handler, MuCPtr userdata);
void (*set_undef_func_handler)(MuVM *mvm, MuUndefFuncHandler undef_func_handler, MuCPtr userdata);
};
// A local context. It can only be used by one thread at a time. It holds many
......@@ -211,6 +207,7 @@ struct MuCtx {
void (*kill_stack)(MuCtx *ctx, MuStackRefValue stack);
// Stack introspection
MuID (*cur_func )(MuCtx *ctx, MuStackRefValue stack, int frame);
MuID (*cur_func_ver )(MuCtx *ctx, MuStackRefValue stack, int frame);
MuID (*cur_inst )(MuCtx *ctx, MuStackRefValue stack, int frame);
void (*dump_keepalives)(MuCtx *ctx, MuStackRefValue stack, int frame, MuValue *results);
......
......@@ -18,25 +18,32 @@ means "Mu thread" unless explicitly stated otherwise.
Concepts
========
A **stack** is the context of nested or recursive activations of functions. A
stack has many **frames**, each of which is the context of one function
A **stack** is the context of nested or recursive activations of functions.
NOTE: "Stack" here means the "control stack", or more precisely the
"context" of execution. On a concrete machine, the context includes not only
the stack, but also the CPU/register states. Mu abstracts the CPU state,
modelling it as part of the state of the stack-top frame.
A stack has many **frames**, each of which is the context of one function
activation. A frame contains the states of all local variables (parameters and
instructions), the program counter and alloca cells (see `<uvm-memory>`__). Each
frame is associated to a version of a function.
instructions), the program counter and alloca cells (see `Mu and the Memory
<uvm-memory>`__). Each frame is associated with a *version* of a function.
NOTE: Because Mu allows function redefinition, a function may be redefined
and newly created function activations (newly called functions) use the new
definition. But any existing function activations still use the old
definition, thus a frame is only bound to a particular version of a
function, not just a function. This is very important because Mu cannot
magically translate the state of any old function activation to a new one. A
redefined function may even have completely different meaning from the old
one. Mu allows crazy things like redefining a factorial function to a
Fibonacci function.
During on-stack replacement, Mu can tell the client which version of which
function any frame is executing and the value of KEEPALIVE variables. The
client is responsible for translating the states.
by the client, and newly created function activations (newly called
functions) will use the new definition. But any existing function
activations will still use their old definitions, thus a frame is only bound
to a particular version of a function, not just a function. This is very
important because Mu cannot magically translate the state of any old
function activation to a new one. A redefined function may even have
completely different meaning from the old one. Mu allows the client to do
crazy things like redefining a factorial function to a Fibonacci function.
During on-stack replacement, the Mu client API can tell the client which
version of which function any frame is executing and the value of KEEPALIVE
variables. The client is responsible for translating the Mu-level states to
the high-level language states.
A **thread** is the unit of CPU scheduling. A thread can be **bound** to a
stack, in which case the thread executes using the stack as its context.
......@@ -98,9 +105,10 @@ 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``.
When a stack is created, a Mu function and all of its parameter must be
provided. The stack will contain a frame created for this function. This frame
is called the **stack-bottom frame** and the function is called the
**stack-bottom function**.
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
......@@ -206,7 +214,8 @@ On-stack Replacement
The client can pop and push frames from or to a stack. The ``pop_frame`` API
function pops the top frame from a stack. The ``push_frame`` function creates a
new frame for a given function and its arguments on the top of a stack.
new frame on the top of the stack, using the current version (as seen by the
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
......
This diff is collapsed.
......@@ -47,7 +47,6 @@ A Mu instance is represented as a pointer to the struct ``MuVM``::
MuID (*id_of )(MuVM *mvm, MuName name);
MuName (*name_of)(MuVM *mvm, MuID id);
void (*set_trap_handler )(MuVM *mvm, MuTrapHandler trap_handler, MuCPtr userdata);
void (*set_undef_func_handler)(MuVM *mvm, MuUndefFuncHandler undef_func_handler, MuCPtr userdata);
};
The client interacts with Mu for almost all tasks through **client contexts**,
......@@ -190,7 +189,7 @@ In the following functions, the first argument ``mvm`` must be the same
The ``newcontext`` function creates a new client context.
For Lua users: This is similar to ``lua_newstate``, but multiple client
agents share the Mu memory and can be used concurrently.
contexts share the Mu memory and can be used concurrently.
::
......@@ -204,12 +203,10 @@ has undefined behaviour.
::
void (*set_trap_handler )(MuVM *mvm, MuTrapHandler trap_handler, MuCPtr userdata);
void (*set_undef_func_handler)(MuVM *mvm, MuUndefFuncHandler undef_func_handler, MuCPtr userdata);
The ``set_trap_handler`` function sets the handler for traps. The
``set_undef_func_handler`` function sets the handler for undefined functions.
See the *Trap and Undefined Function Handling* section below for more
information.
The ``set_trap_handler`` function sets the handler for traps. This overrides the
trap handler registered by the ``@uvm.meta.set_trap_handler`` instruction. See
the *Trap Handling* section below for more information.
``userdata`` will be passed to the handlers as the last element when they are
called.
......@@ -709,20 +706,26 @@ Stack introspection
::
MuID (*cur_func )(MuCtx *ctx, MuStackRefValue stack, int frame);
MuID (*cur_func_ver )(MuCtx *ctx, MuStackRefValue stack, int frame);
MuID (*cur_inst )(MuCtx *ctx, MuStackRefValue stack, int frame);
void (*dump_keepalives)(MuCtx *ctx, MuStackRefValue stack, int frame, MuValue *results);
* ``cur_func_ver`` returns the ID of the current function version of ``frame``
* ``cur_func`` returns the ID of the current function of ``frame``
in ``stack``. Returns 0 if the frame is native.
* ``cur_func_ver`` returns the ID of the current function version of ``frame``
in ``stack``. Returns 0 if the frame is native, or the function of the frame
is undefined.
* ``cur_inst`` returns the ID of the current instruction of ``frame`` in
``stack``. Returns 0 if the frame is just created or if the frame is native.
``stack``. Returns 0 if the frame is just created, its function is undefined,
or the frame is native.
* ``dump_keepalives`` dumps the values of the keep-alive variables of the
current instruction in the selected frame. Cannot called on native frames.
As many handles as the keep-alive variables are written in the ``results``
array.
current instruction in the selected frame. If the function is undefined, the
arguments are the keep-alive variables. Cannot be used on native frames. As
many handles as the keep-alive variables are written in the ``results`` array.
..
......@@ -882,16 +885,15 @@ type.
Implementations may define other calling conventions in addition to
``MU_DEFAULT``.
Trap and Undefined Function Handling
====================================
Trap Handling
=============
A trap handler is called when a ``TRAP`` or ``WATCHPOINT`` instruction is
executed. An undefined function handler is called when a function without
version is either called or used by the ``NEWSTACK`` instruction or the
``new_stack`` API function.
executed. It is also implicitly called when executing an undefined function.
Mu is allowed to call these handlers concurrently. Mu does not guarantee the
order in which these handlers are called.
order in which these handlers are called, but guarantees the happen-before
relation between the trap and the handler. See `Memory Model <memory-model>`__.
The Mu thread that triggers the trap is temporarily unbound from the stack.
......@@ -942,18 +944,6 @@ Before returning, the trap handler should set ``*result``:
In all cases, if ``*new_stack``, ``*value`` and/or ``*exception`` are used, they
must be set and must be held by ``ctx``.
The signature of undefined function handlers is::
typedef void (*MuUndefFuncHandler)(MuCtx *ctx, MuID func_id, MuCPtr userdata);
``func_id`` is the ID of the callee function. ``userdata`` is the pointer
provided when registering the handler.
After returning, the Mu program will re-execute the same instruction again.
NOTE: This means that the client must define the undefined function, or Mu
will keep calling this call-back again and again.
Signal Handling
===============
......
......@@ -60,7 +60,7 @@ Here is an example of Mu IR in the text form::
BRANCH %head
%exit:
RET <@i64> %a
RET %a
}
.expose @gcd_native = @gcd < DEFAULT > @i64_0
......@@ -173,7 +173,7 @@ bundle.
@main_v1.rv = ADD <@i32> @main_v1.argc @i32_1
BRANCH @main_v1.bb1
@main_v1.bb1:
RET <@i32> @main_v1.rv
RET @main_v1.rv
}
A **local name** begins with ``%``. Parameters, basic blocks and instructions,
......@@ -188,7 +188,7 @@ name of the form ``%localname`` within a function definition of version
%rv = ADD <@i32> %argc @i32_1
BRANCH %bb1
%bb1:
RET <@i32> %rv
RET %rv
}
In the above example:
......@@ -205,7 +205,7 @@ name of the form ``%localname`` within a function definition of version
is allowed, though not recommended::
@main_v1.rv = ADD <@i32> %argc @i32_1
RET <@i32> %rv
RET %rv
..
......@@ -567,6 +567,26 @@ where:
* ``Name`` is a global name for the function and
* ``Sig`` is a global name for the signature of the function.
When executing a function without version, it behaves as if it has a hidden
version defined as::
.funcdef Name VERSION NoVersion <Sig> (ParamList) {
%entry:
TRAP <@void> KEEPALIVE (ParamList)
TAILCALL <Sig> Name (ParamList)
}
That is, it will trap to the client, using all variables in the parameter list
of the function as the keep-alive variables. If the stack is ever rebound
passing the expected ``void`` value, it will try to tail-call the same function
(**Not necessarily the same hidden version!** It may have been defined by the
client in the trap!) using the same arguments. If an exception is thrown when
rebound, the ``TRAP`` will re-throw it to the parent frame. The ``cur_func`` API
will return the ID of the function. This hidden version is still not a real
version, so the ``cur_func_ver`` API will return 0. The ``TRAP`` is not a real
instruction, either, so the ``cur_inst`` API will also return 0.
``dump_keepalives`` will dump the arguments.
It is an error to have multiple function declarations and/or function
definitions of the same function name in the same bundle.
......@@ -626,7 +646,7 @@ instruction is defined separately in `<instruction-set>`__.
BRANCH %head
%exit:
RET <@i64> %a
RET %a
}
..
......
......@@ -139,7 +139,7 @@ A **root** is an object reference or internal reference in:
* any global cell, or
* any bound Mu stacks, or
* the registry in any client agent.
* any values held by any client contexts in the API.
A live stack contains references in its alloca cells and live local SSA
variables. A dead stack contains no references. A thread can strongly reach its
......
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