poke-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[COMMITTED] Assembler statements and expressions in Poke


From: Jose E. Marchesi
Subject: [COMMITTED] Assembler statements and expressions in Poke
Date: Sun, 09 Oct 2022 00:02:47 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux)


Poke now provides two ways to execute PVM assembler instructions: an
'asm' expression and an 'asm' statement.

"asm statements" take the following form:

     asm (TEMPLATE [: OUTPUTS [: INPUTS]])

where TEMPLATE is the written form of a PVM assembler program, OUTPUTS a
comma separated list of l-values ans INPUTS a comma separated list of
expressions.

   Before the assembler program in TEMPLATE is executed, the INPUTS (if
any) are pushed on the stack.  After the assembler program is executed,
it is expected to have left the same number of elements on the stack
than OUTPUTS specified.  These values are then assigned to the outputs,
in reverse order.

   Example:

     var result = 0;
     asm ("addi; nip2" : result : 10, 20);

After the execution of the 'asm' statement above, the value 30 is stored
in the variable 'result'.

"asm expressions" take the form:

     asm TYPE: (TEMPLATE [: INPUTS])

where TYPE is a simple type specifier and TEMPLATE and INPUTS have the
same meaning than in 'asm' statements.  A single output is always
expected to be generated on the stack, of type TYPE.

   Example:

     (poke) asm int: ("addi; nip2" : 10, 20) + 2
     32

   In both 'asm' statements and expressions the compiler generates code
that tries to determine whether the execution of the assembler code
results in the original stack being underflown or overflown.  If this is
found to happen an exception 'E_stack' is raised.

   'E_stack' is also raised in case an 'asm' statement or expression
leaves a non-representable PVM value as an output.  This is for example
the case of 'PVM_NULL', which can't be represented in Poke.

   Note however that the above mentioned tests are not perfect, and it
is generally possible to crash the system using 'asm' statements or
expressions.

2022-10-08  Jose E. Marchesi  <jemarch@gnu.org>

        * libpoke/pkl-tab.y: New token ASM.
        Rule for ASM statements and expressions.
        * libpoke/pkl-lex.l: Recognize token ASM.
        * libpoke/pkl-ast.h (PKL_AST_ASM_STMT_TEMPLATE): Define.
        (PKL_AST_ASM_STMT_INPUTS): Likewise.
        (PKL_AST_ASM_STMT_OUTPUTS): Likewise.
        (PKL_AST_ASM_STMT_EXPANDED_TEMPLATE): Likewise.
        (PKL_AST_ASM_EXP_TEMPLATE): Likewise.
        (PKL_AST_ASM_EXP_TYPE): Likewise.
        (PKL_AST_ASM_EXP_INPUTS): Likewise.
        (PKL_AST_ASM_EXP_EXPANDED_TEMPLATE): Likewise.
        (struct pkl_ast_asm_stmt): New struct.
        (struct pkl_ast_asm_exp): Likewise.
        (union pkl_ast_node): New fields asm_exp and asm_stmt.
        * libpoke/pkl-ast.c (pkl_ast_make_asm_stmt): New function.
        (pkl_ast_make_asm_exp): Likewise.
        (pkl_ast_node_free): Handle asm statement and expression nodes.
        (pkl_ast_print_1): Likewise.
        (pkl_ast_make_ass_stmt): Allow building ass_stmt AST nodes without
        r-value.
        * libpoke/pkl-pass.c (pkl_do_pass_1): Traverse asm statement and
        asm expression nodes.
        (pkl_do_pass_1): `exp' may be NULL in ass_stmt.
        * libpoke/pkl-trans.c (pkl_trans1_ps_asm_stmt): New handler.
        (pkl_trans2_ps_asm_stmt): Likewise.
        (pkl_trans2_ps_asm_exp): Likewise.
        (pkl_phase_trans1): Register handler.
        (pkl_phase_trans2): Register handlers.
        * libpoke/pkl-typify.c  (pkl_typify1_ps_asm_stmt): New handler.
        (pkl_typify1_ps_asm_exp): Likewise.
        (pkl_phase_typify1): Register new handlers.
        (pkl_typify1_ps_ass_stmt): `exp' may be NULL.
        * libpoke/pkl-promo.c (pkl_promo_ps_ass_stmt): Do not promote
        anything if `exp' is NULL.
        * libpoke/pkl-gen.c (pkl_gen_pr_asm_exp): New handler.
        (pkl_gen_pr_asm_stmt): Likewise.
        (pkl_phase_gen): Register handlers.
        (pkl_gen_pr_ass_stmt): Do not subpass in EXP if it is null.
        * libpoke/pkl-asm.c (pkl_asm_from_string): New function.
        * libpoke/pvm-program.h: Prototype for pvm_program_parse_from_string.
        * libpoke/pvm-program.c (pvm_program_parse_from_string): New function.
        * libpoke/pkl-rt.pk: New exception E_stack.
        * libpoke/libpoke.h (PK_EC_STACK): Define.
        * libpoke/pvm.h (PVM_E_STACK): Likewise.
        (PVM_E_STACK_NAME): Likewise.
        (PVM_E_STACK_ESTATUS): Likewise.
        * testsuite/poke.pkl/asm-stmt-diag-1.pk: New test.
        * testsuite/poke.pkl/asm-stmt-diag-2.pk: Likewise.
        * testsuite/poke.pkl/asm-stmt-diag-3.pk: Likewise.
        * testsuite/poke.pkl/asm-stmt-diag-4.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-diag-1.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-diag-2.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-diag-3.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-diag-4.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-1.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-2.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-3.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-4.pk: Likewise.
        * testsuite/poke.pkl/asm-exp-5.pk: Likewise.
        * testsuite/poke.pkl/asm-stmt-1.pk: Likewise.
        * testsuite/poke.pkl/asm-stmt-2.pk: Likewise.
        * testsuite/poke.pkl/asm-stmt-3.pk: Likewise.
        * testsuite/poke.pkl/asm-stmt-4.pk: Likewise.
        * testsuite/poke.pkl/asm-stmt-5.pk: Likewise.
        * testsuite/poke.pkl/asm-stmt-6.pk: Likewise.
        * testsuite/Makefile.am (EXTRA_DIST): Add new tests.
        * doc/poke.texi (Assembler): New chapter.
---
 ChangeLog                             |  70 ++++++++++++++
 doc/poke.texi                         |  74 +++++++++++++++
 libpoke/libpoke.h                     |   1 +
 libpoke/pkl-asm.c                     |   7 ++
 libpoke/pkl-asm.h                     |   3 +
 libpoke/pkl-ast.c                     |  93 ++++++++++++++++++-
 libpoke/pkl-ast.h                     |  82 +++++++++++++++-
 libpoke/pkl-gen.c                     | 129 +++++++++++++++++++++++++-
 libpoke/pkl-lex.l                     |   1 +
 libpoke/pkl-pass.c                    |  17 +++-
 libpoke/pkl-promo.c                   |   4 +
 libpoke/pkl-rt.pk                     |   3 +
 libpoke/pkl-tab.y                     |  29 ++++++
 libpoke/pkl-trans.c                   |  72 ++++++++++++++
 libpoke/pkl-typify.c                  |  71 +++++++++++++-
 libpoke/pvm-program.c                 |  46 +++++++++
 libpoke/pvm-program.h                 |  12 +++
 libpoke/pvm.h                         |   4 +
 testsuite/Makefile.am                 |  19 ++++
 testsuite/poke.pkl/asm-exp-1.pk       |   5 +
 testsuite/poke.pkl/asm-exp-2.pk       |   5 +
 testsuite/poke.pkl/asm-exp-3.pk       |   5 +
 testsuite/poke.pkl/asm-exp-4.pk       |   4 +
 testsuite/poke.pkl/asm-exp-5.pk       |   4 +
 testsuite/poke.pkl/asm-exp-diag-1.pk  |   5 +
 testsuite/poke.pkl/asm-exp-diag-2.pk  |   6 ++
 testsuite/poke.pkl/asm-exp-diag-3.pk  |   5 +
 testsuite/poke.pkl/asm-exp-diag-4.pk  |   5 +
 testsuite/poke.pkl/asm-stmt-1.pk      |   4 +
 testsuite/poke.pkl/asm-stmt-2.pk      |  10 ++
 testsuite/poke.pkl/asm-stmt-3.pk      |  12 +++
 testsuite/poke.pkl/asm-stmt-4.pk      |  12 +++
 testsuite/poke.pkl/asm-stmt-5.pk      |   6 ++
 testsuite/poke.pkl/asm-stmt-6.pk      |   6 ++
 testsuite/poke.pkl/asm-stmt-diag-1.pk |   5 +
 testsuite/poke.pkl/asm-stmt-diag-2.pk |   9 ++
 testsuite/poke.pkl/asm-stmt-diag-3.pk |   5 +
 testsuite/poke.pkl/asm-stmt-diag-4.pk |   6 ++
 38 files changed, 847 insertions(+), 9 deletions(-)
 create mode 100644 testsuite/poke.pkl/asm-exp-1.pk
 create mode 100644 testsuite/poke.pkl/asm-exp-2.pk
 create mode 100644 testsuite/poke.pkl/asm-exp-3.pk
 create mode 100644 testsuite/poke.pkl/asm-exp-4.pk
 create mode 100644 testsuite/poke.pkl/asm-exp-5.pk
 create mode 100644 testsuite/poke.pkl/asm-exp-diag-1.pk
 create mode 100644 testsuite/poke.pkl/asm-exp-diag-2.pk
 create mode 100644 testsuite/poke.pkl/asm-exp-diag-3.pk
 create mode 100644 testsuite/poke.pkl/asm-exp-diag-4.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-1.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-2.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-3.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-4.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-5.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-6.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-diag-1.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-diag-2.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-diag-3.pk
 create mode 100644 testsuite/poke.pkl/asm-stmt-diag-4.pk

diff --git a/ChangeLog b/ChangeLog
index 7835c1d9..7123eedc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,73 @@
+2022-10-08  Jose E. Marchesi  <jemarch@gnu.org>
+
+       * libpoke/pkl-tab.y: New token ASM.
+       Rule for ASM statements and expressions.
+       * libpoke/pkl-lex.l: Recognize token ASM.
+       * libpoke/pkl-ast.h (PKL_AST_ASM_STMT_TEMPLATE): Define.
+       (PKL_AST_ASM_STMT_INPUTS): Likewise.
+       (PKL_AST_ASM_STMT_OUTPUTS): Likewise.
+       (PKL_AST_ASM_STMT_EXPANDED_TEMPLATE): Likewise.
+       (PKL_AST_ASM_EXP_TEMPLATE): Likewise.
+       (PKL_AST_ASM_EXP_TYPE): Likewise.
+       (PKL_AST_ASM_EXP_INPUTS): Likewise.
+       (PKL_AST_ASM_EXP_EXPANDED_TEMPLATE): Likewise.
+       (struct pkl_ast_asm_stmt): New struct.
+       (struct pkl_ast_asm_exp): Likewise.
+       (union pkl_ast_node): New fields asm_exp and asm_stmt.
+       * libpoke/pkl-ast.c (pkl_ast_make_asm_stmt): New function.
+       (pkl_ast_make_asm_exp): Likewise.
+       (pkl_ast_node_free): Handle asm statement and expression nodes.
+       (pkl_ast_print_1): Likewise.
+       (pkl_ast_make_ass_stmt): Allow building ass_stmt AST nodes without
+       r-value.
+       * libpoke/pkl-pass.c (pkl_do_pass_1): Traverse asm statement and
+       asm expression nodes.
+       (pkl_do_pass_1): `exp' may be NULL in ass_stmt.
+       * libpoke/pkl-trans.c (pkl_trans1_ps_asm_stmt): New handler.
+       (pkl_trans2_ps_asm_stmt): Likewise.
+       (pkl_trans2_ps_asm_exp): Likewise.
+       (pkl_phase_trans1): Register handler.
+       (pkl_phase_trans2): Register handlers.
+       * libpoke/pkl-typify.c  (pkl_typify1_ps_asm_stmt): New handler.
+       (pkl_typify1_ps_asm_exp): Likewise.
+       (pkl_phase_typify1): Register new handlers.
+       (pkl_typify1_ps_ass_stmt): `exp' may be NULL.
+       * libpoke/pkl-promo.c (pkl_promo_ps_ass_stmt): Do not promote
+       anything if `exp' is NULL.
+       * libpoke/pkl-gen.c (pkl_gen_pr_asm_exp): New handler.
+       (pkl_gen_pr_asm_stmt): Likewise.
+       (pkl_phase_gen): Register handlers.
+       (pkl_gen_pr_ass_stmt): Do not subpass in EXP if it is null.
+       * libpoke/pkl-asm.c (pkl_asm_from_string): New function.
+       * libpoke/pvm-program.h: Prototype for pvm_program_parse_from_string.
+       * libpoke/pvm-program.c (pvm_program_parse_from_string): New function.
+       * libpoke/pkl-rt.pk: New exception E_stack.
+       * libpoke/libpoke.h (PK_EC_STACK): Define.
+       * libpoke/pvm.h (PVM_E_STACK): Likewise.
+       (PVM_E_STACK_NAME): Likewise.
+       (PVM_E_STACK_ESTATUS): Likewise.
+       * testsuite/poke.pkl/asm-stmt-diag-1.pk: New test.
+       * testsuite/poke.pkl/asm-stmt-diag-2.pk: Likewise.
+       * testsuite/poke.pkl/asm-stmt-diag-3.pk: Likewise.
+       * testsuite/poke.pkl/asm-stmt-diag-4.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-diag-1.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-diag-2.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-diag-3.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-diag-4.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-1.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-2.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-3.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-4.pk: Likewise.
+       * testsuite/poke.pkl/asm-exp-5.pk: Likewise.
+       * testsuite/poke.pkl/asm-stmt-1.pk: Likewise.
+       * testsuite/poke.pkl/asm-stmt-2.pk: Likewise.
+       * testsuite/poke.pkl/asm-stmt-3.pk: Likewise.
+       * testsuite/poke.pkl/asm-stmt-4.pk: Likewise.
+       * testsuite/poke.pkl/asm-stmt-5.pk: Likewise.
+       * testsuite/poke.pkl/asm-stmt-6.pk: Likewise.
+       * testsuite/Makefile.am (EXTRA_DIST): Add new tests.
+       * doc/poke.texi (Assembler): New chapter.
+
 2022-10-06  Jose E. Marchesi  <jemarch@gnu.org>
 
        * poke/pk-tracer.pk (pk_tv_action_field_mapped): Get a Pk_Type as
diff --git a/doc/poke.texi b/doc/poke.texi
index 9f9e3491..d1f6a177 100644
--- a/doc/poke.texi
+++ b/doc/poke.texi
@@ -224,6 +224,7 @@ The Poke Language
 * Loops::                      Statements to iterate on conditions.
 * Expression Statements::      Using expressions for their side-effects.
 * Functions::                  Procedural abstraction.
+* Assembler::                   Assembler instructions with Poke operands.
 * Endianness::                 Byte ordering.
 * Mapping::                    Accessing IO spaces.
 * Exception Handling::         Dealing with exceptional conditions.
@@ -9187,6 +9188,7 @@ The IO space to where write the scrabbled data.  Defaults 
to
 * Loops::                      Statements to iterate on conditions.
 * Expression Statements::      Using expressions for their side-effects.
 * Functions::                  Procedural abstraction.
+* Assembler::                   Assembler instructions with Poke operands.
 * Endianness::                 Byte ordering.
 * Mapping::                    Accessing IO spaces.
 * Exception Handling::         Dealing with exceptional conditions.
@@ -12818,6 +12820,78 @@ The following attributes are defined for function 
values.
 Gives an offset @code{0#B}, by convention.
 @end table
 
+@node Assembler
+@section Assembler
+@cindex asm, statement
+@cindex asm, expression
+@cindex assembler
+
+Poke provides two ways to execute PVM assembler instructions: an
+@code{asm} expression and an @code{asm} statement.
+
+@noindent
+@dfn{asm statements} take the following form:
+
+@example
+asm (@var{template} [: @var{outputs} [: @var{inputs}]])
+@end example
+
+@noindent
+where @var{template} is the written form of a PVM assembler program,
+@var{outputs} a comma separated list of l-values ans @var{inputs} a
+comma separated list of expressions.
+
+Before the assembler program in @var{template} is executed, the
+@var{inputs} (if any) are pushed on the stack.  After the assembler
+program is executed, it is expected to have left the same number of
+elements on the stack than @var{outputs} specified.  These values are
+then assigned to the outputs, in reverse order.
+
+Example:
+
+@example
+var result = 0;
+asm ("addi; nip2" : result : 10, 20);
+@end example
+
+@noindent
+After the execution of the @code{asm} statement above, the value 30 is
+stored in the variable @code{result}.
+
+@noindent
+@dfn{asm expressions} take the form:
+
+@example
+asm @var{type}: (@var{template} [: @var{inputs}])
+@end example
+
+@noindent
+where @var{type} is a simple type specifier and @var{template} and
+@var{inputs} have the same meaning than in @code{asm} statements.  A
+single output is always expected to be generated on the stack, of type
+@var{type}.
+
+Example:
+
+@example
+(poke) asm int: ("addi; nip2" : 10, 20) + 2
+32
+@end example
+
+In both @code{asm} statements and expressions the compiler generates
+code that tries to determine whether the execution of the assembler
+code results in the original stack being underflown or overflown.  If
+this is found to happen an exception @code{E_stack} is raised.
+
+@code{E_stack} is also raised in case an @code{asm} statement or
+expression leaves a non-representable PVM value as an output.  This is
+for example the case of @code{PVM_NULL}, which can't be represented in
+Poke.
+
+Note however that the above mentioned tests are not perfect, and it is
+generally possible to crash the system using @code{asm} statements or
+expressions.
+
 @node Endianness
 @section Endianness
 @cindex endianness
diff --git a/libpoke/libpoke.h b/libpoke/libpoke.h
index 966445c3..3a76f06a 100644
--- a/libpoke/libpoke.h
+++ b/libpoke/libpoke.h
@@ -62,6 +62,7 @@ typedef uint64_t pk_val;
 #define PK_EC_ASSERT       16
 #define PK_EC_OVERFLOW     17
 #define PK_EC_PERM         18
+#define PK_EC_STACK        19
 
 struct pk_color
 {
diff --git a/libpoke/pkl-asm.c b/libpoke/pkl-asm.c
index 665e91a6..565bc8b7 100644
--- a/libpoke/pkl-asm.c
+++ b/libpoke/pkl-asm.c
@@ -29,6 +29,7 @@
 #include "pkl-asm.h"
 #include "pkl-env.h"
 #include "pvm-alloc.h"
+#include "pvm-program.h"
 
 /* Code generated by RAS is used to implement many macro-instructions.
    Configure it to use the right assembler, and include the assembled
@@ -2184,3 +2185,9 @@ pkl_asm_label (pkl_asm pasm, pvm_program_label label)
 {
   pvm_program_append_label (pasm->program, label);
 }
+
+void
+pkl_asm_from_string (pkl_asm pasm, const char *str)
+{
+  pvm_program_parse_from_string (str, pasm->program);
+}
diff --git a/libpoke/pkl-asm.h b/libpoke/pkl-asm.h
index 618781dc..1ac7e5af 100644
--- a/libpoke/pkl-asm.h
+++ b/libpoke/pkl-asm.h
@@ -254,4 +254,7 @@ pvm_program_label pkl_asm_fresh_label (pkl_asm pasm);
 /* Append a label.  */
 void pkl_asm_label (pkl_asm pasm, pvm_program_label label);
 
+/* Assembly from a buffer containing PVM assembly code.  */
+void pkl_asm_from_string (pkl_asm pasm, const char *str);
+
 #endif /* PKL_ASM_H */
diff --git a/libpoke/pkl-ast.c b/libpoke/pkl-ast.c
index 85131ea2..62266c16 100644
--- a/libpoke/pkl-ast.c
+++ b/libpoke/pkl-ast.c
@@ -1931,6 +1931,24 @@ pkl_ast_make_lambda (pkl_ast ast, pkl_ast_node function)
   return lambda;
 }
 
+/* Build and return an AST node for an `asm' expression.  */
+pkl_ast_node
+pkl_ast_make_asm_exp (pkl_ast ast, pkl_ast_node type,
+                      pkl_ast_node template, pkl_ast_node inputs)
+{
+  pkl_ast_node asm_exp = pkl_ast_make_node (ast, PKL_AST_ASM_EXP);
+
+  assert (type);
+  assert (template);
+
+  PKL_AST_ASM_EXP_TEMPLATE (asm_exp) = ASTREF (template);
+  PKL_AST_ASM_EXP_TYPE (asm_exp) = ASTREF (type);
+  if (inputs)
+    PKL_AST_ASM_EXP_INPUTS (asm_exp) = ASTREF (inputs);
+
+  return asm_exp;
+}
+
 /* Build and return an AST node for a compound statement.  */
 
 pkl_ast_node
@@ -1966,10 +1984,11 @@ pkl_ast_make_ass_stmt (pkl_ast ast, pkl_ast_node lvalue,
   pkl_ast_node ass_stmt = pkl_ast_make_node (ast,
                                              PKL_AST_ASS_STMT);
 
-  assert (lvalue && exp);
+  assert (lvalue);
 
   PKL_AST_ASS_STMT_LVALUE (ass_stmt) = ASTREF (lvalue);
-  PKL_AST_ASS_STMT_EXP (ass_stmt) = ASTREF (exp);
+  if (exp)
+    PKL_AST_ASS_STMT_EXP (ass_stmt) = ASTREF (exp);
 
   return ass_stmt;
 }
@@ -2186,6 +2205,24 @@ pkl_ast_make_raise_stmt (pkl_ast ast, pkl_ast_node exp)
   return raise_stmt;
 }
 
+/* Build and return an AST node for an `asm' statement.  */
+
+pkl_ast_node
+pkl_ast_make_asm_stmt (pkl_ast ast, pkl_ast_node template,
+                       pkl_ast_node inputs, pkl_ast_node outputs)
+{
+  pkl_ast_node asm_stmt = pkl_ast_make_node (ast,
+                                             PKL_AST_ASM_STMT);
+
+  assert (template);
+  PKL_AST_ASM_STMT_TEMPLATE (asm_stmt) = ASTREF (template);
+  if (inputs)
+    PKL_AST_ASM_STMT_INPUTS (asm_stmt) = ASTREF (inputs);
+  if (outputs)
+    PKL_AST_ASM_STMT_OUTPUTS (asm_stmt) = ASTREF (outputs);
+  return asm_stmt;
+}
+
 /* Build and return an AST node for a PKL program.  */
 
 pkl_ast_node
@@ -2500,6 +2537,20 @@ pkl_ast_node_free (pkl_ast_node ast)
       pkl_ast_node_free (PKL_AST_LAMBDA_FUNCTION (ast));
       break;
 
+    case PKL_AST_ASM_EXP:
+
+      pkl_ast_node_free (PKL_AST_ASM_EXP_TYPE (ast));
+      pkl_ast_node_free (PKL_AST_ASM_EXP_TEMPLATE (ast));
+      free (PKL_AST_ASM_EXP_EXPANDED_TEMPLATE (ast));
+
+      for (t = PKL_AST_ASM_EXP_INPUTS (ast); t; t = n)
+        {
+          n = PKL_AST_CHAIN (t);
+          pkl_ast_node_free (t);
+        }
+
+      break;
+
     case PKL_AST_COMP_STMT:
 
       for (t = PKL_AST_COMP_STMT_STMTS (ast); t; t = n)
@@ -2608,6 +2659,24 @@ pkl_ast_node_free (pkl_ast_node ast)
       pkl_ast_node_free (PKL_AST_RAISE_STMT_EXP (ast));
       break;
 
+    case PKL_AST_ASM_STMT:
+      pkl_ast_node_free (PKL_AST_ASM_STMT_TEMPLATE (ast));
+      free (PKL_AST_ASM_STMT_EXPANDED_TEMPLATE (ast));
+
+      for (t = PKL_AST_ASM_STMT_INPUTS (ast); t; t = n)
+        {
+          n = PKL_AST_CHAIN (t);
+          pkl_ast_node_free (t);
+        }
+
+      for (t = PKL_AST_ASM_STMT_OUTPUTS (ast); t; t = n)
+        {
+          n = PKL_AST_CHAIN (t);
+          pkl_ast_node_free (t);
+        }
+
+      break;
+
     case PKL_AST_NULL_STMT:
       break;
 
@@ -3296,6 +3365,16 @@ pkl_ast_print_1 (FILE *fp, pkl_ast_node ast, int indent)
       PRINT_AST_SUBAST (function, LAMBDA_FUNCTION);
       break;
 
+    case PKL_AST_ASM_EXP:
+      IPRINTF ("ASM_EXP::\n");
+
+      PRINT_COMMON_FIELDS;
+      PRINT_AST_SUBAST (type, ASM_EXP_TYPE);
+      PRINT_AST_SUBAST (template, ASM_EXP_TEMPLATE);
+      PRINT_AST_IMM (expanded_template, ASM_EXP_EXPANDED_TEMPLATE, "%s");
+      PRINT_AST_SUBAST_CHAIN (ASM_EXP_INPUTS);
+      break;
+
     case PKL_AST_FORMAT_ARG:
       IPRINTF ("FORMAT_ARG::\n");
       PRINT_COMMON_FIELDS;
@@ -3439,6 +3518,16 @@ pkl_ast_print_1 (FILE *fp, pkl_ast_node ast, int indent)
       PRINT_AST_SUBAST (raise_stmt_exp, RAISE_STMT_EXP);
       break;
 
+    case PKL_AST_ASM_STMT:
+      IPRINTF ("ASM_STMT::\n");
+
+      PRINT_COMMON_FIELDS;
+      PRINT_AST_IMM (expanded_template, ASM_STMT_EXPANDED_TEMPLATE, "%s");
+      PRINT_AST_SUBAST (asm_stmt_template, ASM_STMT_TEMPLATE);
+      PRINT_AST_SUBAST (asm_stmt_inputs, ASM_STMT_INPUTS);
+      PRINT_AST_SUBAST (asm_stmt_outputs, ASM_STMT_OUTPUTS);
+      break;
+
     case PKL_AST_NULL_STMT:
       IPRINTF ("NULL_STMT::\n");
 
diff --git a/libpoke/pkl-ast.h b/libpoke/pkl-ast.h
index d32abc19..64ffb5f1 100644
--- a/libpoke/pkl-ast.h
+++ b/libpoke/pkl-ast.h
@@ -61,7 +61,8 @@ enum pkl_ast_code
   PKL_AST_FORMAT_ARG,
   PKL_AST_INCRDECR,
   PKL_AST_GCD,
-  PKL_AST_LAST_EXP = PKL_AST_GCD,
+  PKL_AST_ASM_EXP,
+  PKL_AST_LAST_EXP = PKL_AST_ASM_EXP,
   /* Types.  */
   PKL_AST_TYPE,
   PKL_AST_STRUCT_TYPE_FIELD,
@@ -90,7 +91,8 @@ enum pkl_ast_code
   PKL_AST_BREAK_STMT,
   PKL_AST_CONTINUE_STMT,
   PKL_AST_RAISE_STMT,
-  PKL_AST_LAST_STMT = PKL_AST_RAISE_STMT,
+  PKL_AST_ASM_STMT,
+  PKL_AST_LAST_STMT = PKL_AST_ASM_STMT,
   PKL_AST_LAST
 };
 
@@ -1521,6 +1523,38 @@ struct pkl_ast_lambda
 
 pkl_ast_node pkl_ast_make_lambda (pkl_ast ast, pkl_ast_node function);
 
+/* PKL_AST_ASM nodes represent asm expressions.
+
+   TEMPLATE is a string node contaning assembler instructions.
+
+   TYPE is the type of the element that is left at the top of
+   the stack once the program in TEMPLATE is executed.
+
+   INPUTS is a chain of expressions.  The result of these expressions
+   will be pushed in the stack in the given order before executing
+   TEMPLATE.
+
+   EXPANDED_TEMPLATE contains a processed version of TEMPLATE that is
+   ready to be compiled in the PVM.  */
+
+#define PKL_AST_ASM_EXP_TEMPLATE(AST) ((AST)->asm_exp.template)
+#define PKL_AST_ASM_EXP_TYPE(AST) ((AST)->asm_exp.type)
+#define PKL_AST_ASM_EXP_INPUTS(AST) ((AST)->asm_exp.inputs)
+#define PKL_AST_ASM_EXP_EXPANDED_TEMPLATE(AST) 
((AST)->asm_exp.expanded_template)
+
+struct pkl_ast_asm_exp
+{
+  struct pkl_ast_common common;
+
+  union pkl_ast_node *template;
+  union pkl_ast_node *type;
+  union pkl_ast_node *inputs;
+  char *expanded_template;
+};
+
+pkl_ast_node pkl_ast_make_asm_exp (pkl_ast ast, pkl_ast_node type,
+                                   pkl_ast_node template, pkl_ast_node inputs);
+
 /* PKL_AST_INCRDECR nodes represent {pre,post}{increment,decrement}
    expressions.
 
@@ -1645,7 +1679,12 @@ pkl_ast_node pkl_ast_make_null_stmt (pkl_ast ast);
    language.
 
    LVALUE is the l-value of the assignment.
-   EXP is the r-value of the assignment.  */
+   EXP is the r-value of the assignment.
+
+   Note that EXP may be NULL, in which case the assignment statement
+   assumes there is already a r-value on the stack.  This is used to
+   implement the outputs of asm statements.  See
+   pkl_trans1_ps_asm_stmt.  */
 
 #define PKL_AST_ASS_STMT_LVALUE(AST) ((AST)->ass_stmt.lvalue)
 #define PKL_AST_ASS_STMT_EXP(AST) ((AST)->ass_stmt.exp)
@@ -1976,6 +2015,41 @@ struct pkl_ast_raise_stmt
 
 pkl_ast_node pkl_ast_make_raise_stmt (pkl_ast ast, pkl_ast_node exp);
 
+/* PKL_AST_ASM nodes represent `asm' statements, which are used in
+   order to inline PVM assembly in Poke programs.
+
+   TEMPLATE is a string node contaning assembler instructions.
+
+   INPUTS is a chain of expressions.  The result of these expressions
+   will be pushed in the stack in the given order before executing
+   TEMPLATE.
+
+   OUTPUTS is a chain of l-values.  The same number of values on the
+   stack will be assigned to them after executing TEMPLATE, in reverse
+   order.
+
+   EXPANDED_TEMPLATE contains a processed version of TEMPLATE that is
+   ready to be compiled in the PVM.  */
+
+#define PKL_AST_ASM_STMT_TEMPLATE(AST) ((AST)->asm_stmt.template)
+#define PKL_AST_ASM_STMT_INPUTS(AST) ((AST)->asm_stmt.inputs)
+#define PKL_AST_ASM_STMT_OUTPUTS(AST) ((AST)->asm_stmt.outputs)
+#define PKL_AST_ASM_STMT_EXPANDED_TEMPLATE(AST) 
((AST)->asm_stmt.expanded_template)
+
+struct pkl_ast_asm_stmt
+{
+  struct pkl_ast_common common;
+
+  union pkl_ast_node *template;
+  union pkl_ast_node *inputs;
+  union pkl_ast_node *outputs;
+
+  char *expanded_template;
+};
+
+pkl_ast_node pkl_ast_make_asm_stmt (pkl_ast ast, pkl_ast_node template,
+                                    pkl_ast_node inputs, pkl_ast_node outputs);
+
 /* Finally, the `pkl_ast_node' type, which represents an AST node of
    any type.  */
 
@@ -2035,6 +2109,8 @@ union pkl_ast_node
   struct pkl_ast_continue_stmt continue_stmt;
   struct pkl_ast_raise_stmt raise_stmt;
   struct pkl_ast_print_stmt print_stmt;
+  struct pkl_ast_asm_stmt asm_stmt;
+  struct pkl_ast_asm_exp asm_exp;
 };
 
 static inline pkl_ast_node __attribute__ ((always_inline, warn_unused_result))
diff --git a/libpoke/pkl-gen.c b/libpoke/pkl-gen.c
index 1900aeb9..402f80fb 100644
--- a/libpoke/pkl-gen.c
+++ b/libpoke/pkl-gen.c
@@ -937,7 +937,7 @@ PKL_PHASE_END_HANDLER
 
 /*
  * ASS_STMT
- * | EXP
+ * | [EXP]
  * | LVALUE
  */
 
@@ -1007,7 +1007,8 @@ PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_ass_stmt)
   int assigning_computed_field_p = 0;
   const char *computed_field_name = NULL;
 
-  PKL_PASS_SUBPASS (exp);
+  if (exp)
+    PKL_PASS_SUBPASS (exp);
 
   PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_LVALUE);
   PKL_PASS_SUBPASS (lvalue);
@@ -4705,6 +4706,128 @@ PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_cond_exp)
 }
 PKL_PHASE_END_HANDLER
 
+/*
+ * ASM_STMT
+ * | TEMPLATE
+ * | INPUTS
+ * | OUTPUTS
+ */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_asm_stmt)
+{
+  pkl_ast_node asm_stmt = PKL_PASS_NODE;
+  pkl_ast_node input, output;
+
+  /* Push a canary to the stack.  */
+  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
+
+  /* Push the inputs on the stack.  */
+  for (input = PKL_AST_ASM_STMT_INPUTS (asm_stmt);
+       input;
+       input = PKL_AST_CHAIN (input))
+    {
+      PKL_PASS_SUBPASS (input);
+    }
+
+  /* Assembly the expanded asm template.  */
+  pkl_asm_from_string (PKL_GEN_ASM,
+                       PKL_AST_ASM_STMT_EXPANDED_TEMPLATE (asm_stmt));
+
+  /* Generate the output assignments.  */
+  for (output = PKL_AST_ASM_STMT_OUTPUTS (asm_stmt);
+       output;
+       output = PKL_AST_CHAIN (output))
+    {
+      /* If the output on the stack is PVM_NULL then raise
+         E_stack.  */
+      pvm_program_label output_ok_label = pkl_asm_fresh_label (PKL_GEN_ASM);
+
+      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNN, output_ok_label);
+      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
+                    pvm_make_exception (PVM_E_STACK, PVM_E_STACK_NAME,
+                                        PVM_E_STACK_ESTATUS, NULL,
+                                        "null output in asm statement"));
+      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
+      pkl_asm_label (PKL_GEN_ASM, output_ok_label);
+
+      PKL_PASS_SUBPASS (output);
+    }
+
+  /* Check and drop the canary.  */
+  {
+    pvm_program_label canary_ok_label = pkl_asm_fresh_label (PKL_GEN_ASM);
+
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BN, canary_ok_label);
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
+                  pvm_make_exception (PVM_E_STACK, PVM_E_STACK_NAME,
+                                      PVM_E_STACK_ESTATUS, NULL,
+                                      "stack overflow or underflow in asm 
statement"));
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
+    pkl_asm_label (PKL_GEN_ASM, canary_ok_label);
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
+  }
+
+  PKL_PASS_BREAK;
+}
+PKL_PHASE_END_HANDLER
+
+/*
+ * ASM_EXP
+ * | TYPE
+ * | TEMPLATE
+ * | INPUTS  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_asm_exp)
+{
+  pkl_ast_node asm_exp = PKL_PASS_NODE;
+  pkl_ast_node input;
+
+  /* Push a canary to the stack.  */
+  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
+
+  /* Push the inputs on the stack.  */
+  for (input = PKL_AST_ASM_EXP_INPUTS (asm_exp);
+       input;
+       input = PKL_AST_CHAIN (input))
+    {
+      PKL_PASS_SUBPASS (input);
+    }
+
+  /* Assembly the expanded asm template.  */
+  pkl_asm_from_string (PKL_GEN_ASM,
+                       PKL_AST_ASM_EXP_EXPANDED_TEMPLATE (asm_exp));
+
+  /* If the output on the stack is PVM_NULL then raise E_stack.  */
+  pvm_program_label output_ok_label = pkl_asm_fresh_label (PKL_GEN_ASM);
+
+  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNN, output_ok_label);
+  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
+                pvm_make_exception (PVM_E_STACK, PVM_E_STACK_NAME,
+                                    PVM_E_STACK_ESTATUS, NULL,
+                                    "null output in asm expression"));
+  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
+  pkl_asm_label (PKL_GEN_ASM, output_ok_label);
+
+  /* Check and drop the canary.  */
+  {
+    pvm_program_label canary_ok_label = pkl_asm_fresh_label (PKL_GEN_ASM);
+
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BN, canary_ok_label);
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
+                  pvm_make_exception (PVM_E_STACK, PVM_E_STACK_NAME,
+                                      PVM_E_STACK_ESTATUS, NULL,
+                                      "stack overflow or underflow in asm 
expression"));
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
+    pkl_asm_label (PKL_GEN_ASM, canary_ok_label);
+    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
+  }
+
+  /* Now the return value is on the stack.  */
+  PKL_PASS_BREAK;
+}
+PKL_PHASE_END_HANDLER
+
 struct pkl_phase pkl_phase_gen =
   {
    PKL_PHASE_PS_HANDLER (PKL_AST_SRC, pkl_gen_ps_src),
@@ -4759,6 +4882,8 @@ struct pkl_phase pkl_phase_gen =
    PKL_PHASE_PR_HANDLER (PKL_AST_STRUCT_FIELD, pkl_gen_pr_struct_field),
    PKL_PHASE_PS_HANDLER (PKL_AST_STRUCT_REF, pkl_gen_ps_struct_ref),
    PKL_PHASE_PR_HANDLER (PKL_AST_STRUCT_TYPE_FIELD, 
pkl_gen_pr_struct_type_field),
+   PKL_PHASE_PR_HANDLER (PKL_AST_ASM_STMT, pkl_gen_pr_asm_stmt),
+   PKL_PHASE_PR_HANDLER (PKL_AST_ASM_EXP, pkl_gen_pr_asm_exp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_ADD, pkl_gen_ps_op_add),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_SUB, pkl_gen_ps_op_sub),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_MUL, pkl_gen_ps_op_mul),
diff --git a/libpoke/pkl-lex.l b/libpoke/pkl-lex.l
index 0846b30e..5d58437a 100644
--- a/libpoke/pkl-lex.l
+++ b/libpoke/pkl-lex.l
@@ -204,6 +204,7 @@ S ::
   return STR;
  }
 
+"asm"           { return ASM; }
 "pinned"        { return PINNED; }
 "struct"        { return STRUCT; }
 "union"         { return UNION; }
diff --git a/libpoke/pkl-pass.c b/libpoke/pkl-pass.c
index 6ab62c6f..7689d544 100644
--- a/libpoke/pkl-pass.c
+++ b/libpoke/pkl-pass.c
@@ -513,7 +513,8 @@ pkl_do_pass_1 (pkl_compiler compiler,
         PKL_PASS_CHAIN (PKL_AST_COMP_STMT_STMTS (node));
       break;
     case PKL_AST_ASS_STMT:
-      PKL_PASS (PKL_AST_ASS_STMT_EXP (node));
+      if (PKL_AST_ASS_STMT_EXP (node))
+        PKL_PASS (PKL_AST_ASS_STMT_EXP (node));
       PKL_PASS (PKL_AST_ASS_STMT_LVALUE (node));
       break;
     case PKL_AST_IF_STMT:
@@ -566,6 +567,14 @@ pkl_do_pass_1 (pkl_compiler compiler,
       if (PKL_AST_RAISE_STMT_EXP (node))
         PKL_PASS (PKL_AST_RAISE_STMT_EXP (node));
       break;
+    case PKL_AST_ASM_STMT:
+      if (PKL_AST_ASM_STMT_TEMPLATE (node))
+        PKL_PASS (PKL_AST_ASM_STMT_TEMPLATE (node));
+      if (PKL_AST_ASM_STMT_INPUTS (node))
+        PKL_PASS_CHAIN (PKL_AST_ASM_STMT_INPUTS (node));
+      if (PKL_AST_ASM_STMT_OUTPUTS (node))
+        PKL_PASS_CHAIN (PKL_AST_ASM_STMT_OUTPUTS (node));
+      break;
     case PKL_AST_FORMAT_ARG:
       if (PKL_AST_FORMAT_ARG_EXP (node))
         PKL_PASS (PKL_AST_FORMAT_ARG_EXP (node));
@@ -586,6 +595,12 @@ pkl_do_pass_1 (pkl_compiler compiler,
     case PKL_AST_LAMBDA:
       PKL_PASS (PKL_AST_LAMBDA_FUNCTION (node));
       break;
+    case PKL_AST_ASM_EXP:
+      PKL_PASS (PKL_AST_ASM_EXP_TYPE (node));
+      PKL_PASS (PKL_AST_ASM_EXP_TEMPLATE (node));
+      if (PKL_AST_ASM_EXP_INPUTS (node))
+        PKL_PASS_CHAIN (PKL_AST_ASM_EXP_INPUTS (node));
+      break;
     case PKL_AST_INCRDECR:
       PKL_PASS (PKL_AST_INCRDECR_EXP (node));
       if (PKL_AST_INCRDECR_ASS_STMT (node))
diff --git a/libpoke/pkl-promo.c b/libpoke/pkl-promo.c
index 903fc19d..0d8c662e 100644
--- a/libpoke/pkl-promo.c
+++ b/libpoke/pkl-promo.c
@@ -1004,6 +1004,10 @@ PKL_PHASE_BEGIN_HANDLER (pkl_promo_ps_ass_stmt)
   pkl_ast_node lvalue_type = PKL_AST_TYPE (lvalue);
   int restart = 0;
 
+  if (!exp)
+    /* Nothing to promote.  */
+    PKL_PASS_DONE;
+
   /* At this point it is assured that exp_type is promoteable to
      lvalue_type, or typify1 wouldn't have allowed this node to
      pass.  */
diff --git a/libpoke/pkl-rt.pk b/libpoke/pkl-rt.pk
index d66a92b2..1a0a8880 100644
--- a/libpoke/pkl-rt.pk
+++ b/libpoke/pkl-rt.pk
@@ -171,6 +171,7 @@ immutable var EC_exit          = 15;
 immutable var EC_assert        = 16;
 immutable var EC_overflow      = 17;
 immutable var EC_perm          = 18;
+immutable var EC_stack         = 19;
 
 /* Standard exceptions.  */
 
@@ -210,6 +211,8 @@ immutable var E_overflow
   = Exception {code = EC_overflow, name = "overflow", exit_status = 1};
 immutable var E_perm
   = Exception {code = EC_perm, name = "wrong permissions", exit_status = 1};
+immutable var E_stack
+  = Exception {code = EC_stack, name = "invalid stack", exit_status = 1};
 
 /* Registration of user-defined exceptions */
 
diff --git a/libpoke/pkl-tab.y b/libpoke/pkl-tab.y
index 778a8175..212adff6 100644
--- a/libpoke/pkl-tab.y
+++ b/libpoke/pkl-tab.y
@@ -419,6 +419,7 @@ load_module (struct pkl_parser *parser,
 
 /* Reserved words.  */
 
+%token ASM               _("keyword `asm'")
 %token ENUM              _("keyword `enum'")
 %token <integer> PINNED  _("keyword `pinned'")
 %token STRUCT            _("keyword `struct'")
@@ -1207,6 +1208,16 @@ primary:
                   $$ = pkl_ast_make_lambda (pkl_parser->ast, $3);
                   PKL_AST_LOC ($$) = @$;
                 }
+        | ASM simple_type_specifier ':' '(' expression ')'
+                {
+                  $$ = pkl_ast_make_asm_exp (pkl_parser->ast, $2, $5, NULL);
+                  PKL_AST_LOC ($$) = @$;
+                }
+        | ASM simple_type_specifier ':' '(' expression ':' expression_list ')'
+                {
+                  $$ = pkl_ast_make_asm_exp (pkl_parser->ast, $2, $5, $7);
+                  PKL_AST_LOC ($$) = @$;
+                }
         | FORMAT '(' STR format_arg_list ')'
                 {
                   $$ = pkl_ast_make_format (pkl_parser->ast, $3, $4,
@@ -2359,6 +2370,24 @@ simple_stmt:
                                               $1);
                   PKL_AST_LOC ($$) = @$;
                 }
+        | ASM '(' expression ')'
+                {
+                  $$ = pkl_ast_make_asm_stmt (pkl_parser->ast,
+                                              $3, NULL, NULL);
+                  PKL_AST_LOC ($$) = @$;
+                }
+        | ASM '(' expression ':' expression_list ')'
+                {
+                  $$ = pkl_ast_make_asm_stmt (pkl_parser->ast,
+                                              $3, NULL, $5);
+                  PKL_AST_LOC ($$) = @$;
+                }
+        | ASM '(' expression ':' expression_list ':' expression_list ')'
+                {
+                  $$ = pkl_ast_make_asm_stmt (pkl_parser->ast,
+                                              $3, $7, $5);
+                  PKL_AST_LOC ($$) = @$;
+                }
         ;
 
 stmt:
diff --git a/libpoke/pkl-trans.c b/libpoke/pkl-trans.c
index c477bb4d..7242882f 100644
--- a/libpoke/pkl-trans.c
+++ b/libpoke/pkl-trans.c
@@ -34,6 +34,8 @@
 #include "pkl-pass.h"
 #include "pkl-trans.h"
 
+#include "pvm-program.h" /* For pvm_program_expand_asm_template */
+
 /* This file implements several transformation compiler phases which,
    generally speaking, are restartable.  */
 
@@ -1416,6 +1418,43 @@ PKL_PHASE_BEGIN_HANDLER (pkl_trans1_ps_indexer)
 }
 PKL_PHASE_END_HANDLER
 
+/* Reverse list of outputs and transform them into assignments,
+   checking that they are proper l-values.  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_trans1_ps_asm_stmt)
+{
+  pkl_ast_node asm_stmt = PKL_PASS_NODE;
+  pkl_ast_node assignments = NULL, output;
+
+  for (output = PKL_AST_ASM_STMT_OUTPUTS (asm_stmt);
+       output;
+       output = PKL_AST_CHAIN (output))
+    {
+      pkl_ast_node ass_stmt;
+
+      if (!pkl_ast_lvalue_p (output))
+        {
+          PKL_ERROR (PKL_AST_LOC (output),
+                     "asm statement output should be a l-value");
+          PKL_TRANS_PAYLOAD->errors++;
+          PKL_PASS_ERROR;
+        }
+
+      ass_stmt = pkl_ast_make_ass_stmt (PKL_PASS_AST,
+                                        output,
+                                        NULL /* exp */);
+      /* Note the reverse order.  */
+      assignments = pkl_ast_chainon (ass_stmt, assignments);
+    }
+
+  if (assignments)
+    {
+      PKL_AST_ASM_STMT_OUTPUTS (asm_stmt) = ASTREF (assignments);
+      PKL_PASS_RESTART = 1;
+    }
+}
+PKL_PHASE_END_HANDLER
+
 struct pkl_phase pkl_phase_trans1 =
   {
    PKL_PHASE_PS_HANDLER (PKL_AST_SRC, pkl_trans_ps_src),
@@ -1448,6 +1487,7 @@ struct pkl_phase pkl_phase_trans1 =
    PKL_PHASE_PS_HANDLER (PKL_AST_STRUCT_TYPE_FIELD, 
pkl_trans1_ps_struct_type_field),
    PKL_PHASE_PS_HANDLER (PKL_AST_RETURN_STMT, pkl_trans1_ps_return_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_INDEXER, pkl_trans1_ps_indexer),
+   PKL_PHASE_PS_HANDLER (PKL_AST_ASM_STMT, pkl_trans1_ps_asm_stmt),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_ATTR, pkl_trans1_ps_op_attr),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_STRUCT, pkl_trans1_ps_type_struct),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_FUNCTION, pkl_trans1_ps_type_function),
@@ -1787,6 +1827,36 @@ PKL_PHASE_BEGIN_HANDLER (pkl_trans2_ps_ass_stmt)
 }
 PKL_PHASE_END_HANDLER
 
+/* Expand the assembler template in an asm statement.  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_trans2_ps_asm_stmt)
+{
+  pkl_ast_node asm_stmt = PKL_PASS_NODE;
+  pkl_ast_node asm_stmt_template = PKL_AST_ASM_STMT_TEMPLATE (asm_stmt);
+
+  if (PKL_AST_ASM_STMT_EXPANDED_TEMPLATE (asm_stmt) != NULL)
+    PKL_PASS_DONE;
+
+  PKL_AST_ASM_STMT_EXPANDED_TEMPLATE (asm_stmt)
+    = pvm_program_expand_asm_template (PKL_AST_IDENTIFIER_POINTER 
(asm_stmt_template));
+}
+PKL_PHASE_END_HANDLER
+
+/* Expand the assembler template in an asm expression.  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_trans2_ps_asm_exp)
+{
+  pkl_ast_node asm_exp = PKL_PASS_NODE;
+  pkl_ast_node asm_exp_template = PKL_AST_ASM_EXP_TEMPLATE (asm_exp);
+
+  if (PKL_AST_ASM_EXP_EXPANDED_TEMPLATE (asm_exp) != NULL)
+    PKL_PASS_DONE;
+
+  PKL_AST_ASM_EXP_EXPANDED_TEMPLATE (asm_exp)
+    = pvm_program_expand_asm_template (PKL_AST_IDENTIFIER_POINTER 
(asm_exp_template));
+}
+PKL_PHASE_END_HANDLER
+
 struct pkl_phase pkl_phase_trans2 =
   {
    PKL_PHASE_PS_HANDLER (PKL_AST_SRC, pkl_trans_ps_src),
@@ -1801,6 +1871,8 @@ struct pkl_phase pkl_phase_trans2 =
    PKL_PHASE_PS_HANDLER (PKL_AST_CAST, pkl_trans2_ps_cast),
    PKL_PHASE_PS_HANDLER (PKL_AST_INCRDECR, pkl_trans2_ps_incrdecr),
    PKL_PHASE_PS_HANDLER (PKL_AST_ASS_STMT, pkl_trans2_ps_ass_stmt),
+   PKL_PHASE_PS_HANDLER (PKL_AST_ASM_STMT, pkl_trans2_ps_asm_stmt),
+   PKL_PHASE_PS_HANDLER (PKL_AST_ASM_EXP, pkl_trans2_ps_asm_exp),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_OFFSET, pkl_trans2_ps_type_offset),
    PKL_PHASE_PS_HANDLER (PKL_AST_STRUCT_TYPE_FIELD, 
pkl_trans2_ps_struct_type_field),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_ATTR, pkl_trans2_ps_op_attr),
diff --git a/libpoke/pkl-typify.c b/libpoke/pkl-typify.c
index 63fc4419..2079d58f 100644
--- a/libpoke/pkl-typify.c
+++ b/libpoke/pkl-typify.c
@@ -3089,7 +3089,13 @@ PKL_PHASE_BEGIN_HANDLER (pkl_typify1_ps_ass_stmt)
   pkl_ast_node lvalue = PKL_AST_ASS_STMT_LVALUE (ass_stmt);
   pkl_ast_node exp = PKL_AST_ASS_STMT_EXP (ass_stmt);
   pkl_ast_node lvalue_type = PKL_AST_TYPE (lvalue);
-  pkl_ast_node exp_type = PKL_AST_TYPE (exp);
+  pkl_ast_node exp_type;
+
+  if (!exp)
+    /* Nothing to check for.  */
+    PKL_PASS_DONE;
+
+  exp_type = PKL_AST_TYPE (exp);
 
   if (!pkl_ast_type_promoteable_p (exp_type, lvalue_type,
                                    1 /* promote_array_of_any */))
@@ -3246,6 +3252,67 @@ PKL_PHASE_BEGIN_HANDLER (pkl_typify1_ps_op_excond)
 }
 PKL_PHASE_END_HANDLER
 
+/* The template in an asm statement should be of type string.  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_typify1_ps_asm_stmt)
+{
+  pkl_ast_node asm_stmt = PKL_PASS_NODE;
+  pkl_ast_node template = PKL_AST_ASM_STMT_TEMPLATE (asm_stmt);
+  pkl_ast_node template_type = PKL_AST_TYPE (template);
+
+  if (PKL_AST_TYPE_CODE (template_type) != PKL_TYPE_STRING)
+    {
+      char *template_type_str = pkl_type_str (template_type, 1);
+
+      PKL_ERROR (PKL_AST_LOC (template),
+                 "expected string, got %s",
+                 template_type_str);
+      free (template_type_str);
+      PKL_TYPIFY_PAYLOAD->errors++;
+      PKL_PASS_ERROR;
+    }
+}
+PKL_PHASE_END_HANDLER
+
+/* The type of an asm expression is the type specified by the user in
+   the expression.
+
+   Also, the template in the asm expression should be of type
+   string.
+
+   Also, the returned type cannot be void.  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_typify1_ps_asm_exp)
+{
+  pkl_ast_node asm_exp = PKL_PASS_NODE;
+  pkl_ast_node template = PKL_AST_ASM_EXP_TEMPLATE (asm_exp);
+  pkl_ast_node template_type = PKL_AST_TYPE (template);
+  pkl_ast_node type = PKL_AST_ASM_EXP_TYPE (asm_exp);
+
+  if (PKL_AST_TYPE_CODE (template_type) != PKL_TYPE_STRING)
+    {
+      char *template_type_str = pkl_type_str (template_type, 1);
+
+      PKL_ERROR (PKL_AST_LOC (template),
+                 "expected string, got %s",
+                 template_type_str);
+      free (template_type_str);
+      PKL_TYPIFY_PAYLOAD->errors++;
+      PKL_PASS_ERROR;
+    }
+
+  if (PKL_AST_TYPE_CODE (type) == PKL_TYPE_VOID)
+    {
+      PKL_ERROR (PKL_AST_LOC (type),
+                 "asm expression cannot return `void'");
+      PKL_TYPIFY_PAYLOAD->errors++;
+      PKL_PASS_ERROR;
+    }
+
+  PKL_AST_TYPE (asm_exp) = ASTREF (type);
+}
+PKL_PHASE_END_HANDLER
+
 struct pkl_phase pkl_phase_typify1 =
   {
    PKL_PHASE_PS_HANDLER (PKL_AST_SRC, pkl_typify_ps_src),
@@ -3279,6 +3346,8 @@ struct pkl_phase pkl_phase_typify1 =
    PKL_PHASE_PS_HANDLER (PKL_AST_IF_STMT, pkl_typify1_ps_if_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_COND_EXP, pkl_typify1_ps_cond_exp),
    PKL_PHASE_PS_HANDLER (PKL_AST_ASS_STMT, pkl_typify1_ps_ass_stmt),
+   PKL_PHASE_PS_HANDLER (PKL_AST_ASM_EXP, pkl_typify1_ps_asm_exp),
+   PKL_PHASE_PS_HANDLER (PKL_AST_ASM_STMT, pkl_typify1_ps_asm_stmt),
 
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_SIZEOF, pkl_typify1_ps_op_sizeof),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_TYPEOF, pkl_typify1_ps_op_typeof),
diff --git a/libpoke/pvm-program.c b/libpoke/pvm-program.c
index 60482155..274a63ba 100644
--- a/libpoke/pvm-program.c
+++ b/libpoke/pvm-program.c
@@ -21,6 +21,7 @@
 #include <assert.h>
 #include <string.h>
 #include <stdio.h> /* For stdout. */
+#include <xalloc.h> /* For xstrdup.  */
 
 #include "jitter/jitter-print.h"
 
@@ -316,6 +317,51 @@ pvm_disassemble_program_nat (pvm_program program)
                            true, JITTER_OBJDUMP, NULL);
 }
 
+char *
+pvm_program_expand_asm_template (const char *str)
+{
+  /* XXX translate str to handle immediates:
+       "foo"
+       u?int<N>M
+       E_inval, etc.
+     then pass the resulting pointer in the string.
+     but beware of 32-bit: pushlo + push32.  */
+
+  size_t expanded_size = 0, q;
+  const char *p;
+  char *expanded;
+
+  /* First, calculate the size of the expanded string.  */
+  for (p = str; *p != '\0'; ++p)
+    {
+      ++expanded_size;
+    }
+
+  /* Allocate the expanded string.  */
+  expanded = xmalloc (expanded_size + 1);
+
+  /* Now build the expanded string.  */
+  for (p = str, q = 0; *p != '\0'; ++p)
+    {
+      assert (q < expanded_size);
+
+      /* ; -> \n */
+      if (*p == ';')
+        expanded[q++] = '\n';
+      else
+        expanded[q++] = *p;
+    }
+  expanded[expanded_size] = '\0';
+
+  return expanded;
+}
+
+void
+pvm_program_parse_from_string (const char *str, pvm_program program)
+{
+  pvm_parse_mutable_routine_from_string (str, program->routine);
+}
+
 void
 pvm_disassemble_program (pvm_program program)
 {
diff --git a/libpoke/pvm-program.h b/libpoke/pvm-program.h
index 4fcf12a6..7c3705e3 100644
--- a/libpoke/pvm-program.h
+++ b/libpoke/pvm-program.h
@@ -34,4 +34,16 @@ pvm_program_program_point pvm_program_beginning (pvm_program 
program);
 /* Get the jitter routine associated with the program PROGRAM.  */
 pvm_routine pvm_program_routine (pvm_program program);
 
+/* Expand the given PVM assembler template to a form that is
+   acceptable for pvm_program_parse_from_string.
+
+   XXX handle parse errors.  */
+
+char *pvm_program_expand_asm_template (const char *str);
+
+/* Parse PVM instructions from the given string and append them to
+   the given program.  */
+
+void pvm_program_parse_from_string (const char *str, pvm_program program);
+
 #endif /* ! PVM_PROGRAM_H */
diff --git a/libpoke/pvm.h b/libpoke/pvm.h
index e9773813..dd9eed69 100644
--- a/libpoke/pvm.h
+++ b/libpoke/pvm.h
@@ -584,6 +584,10 @@ enum pvm_exit_code
 #define PVM_E_PERM_NAME     "wrong permissions"
 #define PVM_E_PERM_ESTATUS 1
 
+#define PVM_E_STACK        19
+#define PVM_E_STACK_NAME   "invalid stack"
+#define PVM_E_STACK_ESTATUS 1
+
 typedef struct pvm *pvm;
 
 /* Initialize a new Poke Virtual Machine and return it.  */
diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am
index 9848037f..746921f2 100644
--- a/testsuite/Makefile.am
+++ b/testsuite/Makefile.am
@@ -737,6 +737,25 @@ EXTRA_DIST = \
   poke.pkl/arrays-index-diag-5.pk \
   poke.pkl/arrays-initializer-diag-1.pk \
   poke.pkl/arrays-initializer-diag-2.pk \
+  poke.pkl/asm-exp-1.pk \
+  poke.pkl/asm-exp-2.pk \
+  poke.pkl/asm-exp-3.pk \
+  poke.pkl/asm-exp-4.pk \
+  poke.pkl/asm-exp-5.pk \
+  poke.pkl/asm-exp-diag-1.pk \
+  poke.pkl/asm-exp-diag-2.pk \
+  poke.pkl/asm-exp-diag-3.pk \
+  poke.pkl/asm-exp-diag-4.pk \
+  poke.pkl/asm-stmt-1.pk \
+  poke.pkl/asm-stmt-2.pk \
+  poke.pkl/asm-stmt-3.pk \
+  poke.pkl/asm-stmt-4.pk \
+  poke.pkl/asm-stmt-5.pk \
+  poke.pkl/asm-stmt-6.pk \
+  poke.pkl/asm-stmt-diag-1.pk \
+  poke.pkl/asm-stmt-diag-2.pk \
+  poke.pkl/asm-stmt-diag-3.pk \
+  poke.pkl/asm-stmt-diag-4.pk \
   poke.pkl/array-integ-1.pk \
   poke.pkl/array-integ-2.pk \
   poke.pkl/array-integ-3.pk \
diff --git a/testsuite/poke.pkl/asm-exp-1.pk b/testsuite/poke.pkl/asm-exp-1.pk
new file mode 100644
index 00000000..b2dbd545
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-1.pk
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+/* { dg-command { .set obase 10 } } */
+/* { dg-command { asm int<32>: ( "" : 10 ) + 1 } } */
+/* { dg-output "11" } */
diff --git a/testsuite/poke.pkl/asm-exp-2.pk b/testsuite/poke.pkl/asm-exp-2.pk
new file mode 100644
index 00000000..cd69d094
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-2.pk
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+/* { dg-command { .set obase 10 } } */
+/* { dg-command { asm int<32>: ( "addi; nip2" : 10, 20 ) + 1 } } */
+/* { dg-output "31" } */
diff --git a/testsuite/poke.pkl/asm-exp-3.pk b/testsuite/poke.pkl/asm-exp-3.pk
new file mode 100644
index 00000000..d0145457
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-3.pk
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+/* { dg-command { .set obase 10 } } */
+/* { dg-command { asm int<64>: ( "addl; nip2" : 10L, 20L ) + 1 } } */
+/* { dg-output "31L" } */
diff --git a/testsuite/poke.pkl/asm-exp-4.pk b/testsuite/poke.pkl/asm-exp-4.pk
new file mode 100644
index 00000000..0b9ddc36
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-4.pk
@@ -0,0 +1,4 @@
+/* { dg-do run } */
+
+/* { dg-command {try asm int: ("addi; nip" : 10, 20); catch if E_stack { 
printf "caught\n"; } } } */
+/* { dg-output "caught" } */
diff --git a/testsuite/poke.pkl/asm-exp-5.pk b/testsuite/poke.pkl/asm-exp-5.pk
new file mode 100644
index 00000000..e0976811
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-5.pk
@@ -0,0 +1,4 @@
+/* { dg-do run } */
+
+/* { dg-command {try asm int: ("mgetm; nip" : 10); catch if E_stack { printf 
"caught\n"; } } } */
+/* { dg-output "caught" } */
diff --git a/testsuite/poke.pkl/asm-exp-diag-1.pk 
b/testsuite/poke.pkl/asm-exp-diag-1.pk
new file mode 100644
index 00000000..86b041bc
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-diag-1.pk
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+
+var a = asm int<32>:
+  (20 /* { dg-error "expected string" } */
+  );
diff --git a/testsuite/poke.pkl/asm-exp-diag-2.pk 
b/testsuite/poke.pkl/asm-exp-diag-2.pk
new file mode 100644
index 00000000..0bef0ee1
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-diag-2.pk
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+
+var a = asm int<32>:
+  (20 :  /* { dg-error "expected string" } */
+   30
+  );
diff --git a/testsuite/poke.pkl/asm-exp-diag-3.pk 
b/testsuite/poke.pkl/asm-exp-diag-3.pk
new file mode 100644
index 00000000..9e6f3a42
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-diag-3.pk
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+
+var a = asm
+void: /* { dg-error "void" } */
+  ("foo");
diff --git a/testsuite/poke.pkl/asm-exp-diag-4.pk 
b/testsuite/poke.pkl/asm-exp-diag-4.pk
new file mode 100644
index 00000000..8138d782
--- /dev/null
+++ b/testsuite/poke.pkl/asm-exp-diag-4.pk
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+
+var a = asm
+void: /* { dg-error "void" } */
+  ("foo" : 10);
diff --git a/testsuite/poke.pkl/asm-stmt-1.pk b/testsuite/poke.pkl/asm-stmt-1.pk
new file mode 100644
index 00000000..bc4a4359
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-1.pk
@@ -0,0 +1,4 @@
+/* { dg-do run } */
+
+/* { dg-command { asm ("prints" :: "hello" + "\n") } } */
+/* { dg-output {hello} } */
diff --git a/testsuite/poke.pkl/asm-stmt-2.pk b/testsuite/poke.pkl/asm-stmt-2.pk
new file mode 100644
index 00000000..dbacce78
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-2.pk
@@ -0,0 +1,10 @@
+/* { dg-do run } */
+
+var a = 10;
+
+
+asm ("addi;nip2" : a : a, 20);
+
+/* { dg-command {.set obase 10} } */
+/* { dg-command {a} } */
+/* { dg-output "30" } */
diff --git a/testsuite/poke.pkl/asm-stmt-3.pk b/testsuite/poke.pkl/asm-stmt-3.pk
new file mode 100644
index 00000000..be48c454
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-3.pk
@@ -0,0 +1,12 @@
+/* { dg-do run } */
+
+var a = 10;
+var b = 20;
+
+asm ("" : a, b : a, b);
+
+/* { dg-command {.set obase 10} } */
+/* { dg-command {a} } */
+/* { dg-output "10" } */
+/* { dg-command {b} } */
+/* { dg-output "\n20" } */
diff --git a/testsuite/poke.pkl/asm-stmt-4.pk b/testsuite/poke.pkl/asm-stmt-4.pk
new file mode 100644
index 00000000..948924ce
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-4.pk
@@ -0,0 +1,12 @@
+/* { dg-do run } */
+
+var a = 10;
+var b = 20;
+
+asm ("" : a, b : b, a);
+
+/* { dg-command {.set obase 10} } */
+/* { dg-command {a} } */
+/* { dg-output "20" } */
+/* { dg-command {b} } */
+/* { dg-output "\n10" } */
diff --git a/testsuite/poke.pkl/asm-stmt-5.pk b/testsuite/poke.pkl/asm-stmt-5.pk
new file mode 100644
index 00000000..5bc4130b
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-5.pk
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+
+var a = 10;
+
+/* { dg-command {try asm ("mgetm; nip;" : a : 10); catch if E_stack { printf 
"caught\n"; } } } */
+/* { dg-output "caught" } */
diff --git a/testsuite/poke.pkl/asm-stmt-6.pk b/testsuite/poke.pkl/asm-stmt-6.pk
new file mode 100644
index 00000000..62a1827f
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-6.pk
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+
+var a = 10;
+
+/* { dg-command {try asm ("addi; nip;" : a : 10, 20); catch if E_stack { 
printf "caught\n"; } } } */
+/* { dg-output "caught" } */
diff --git a/testsuite/poke.pkl/asm-stmt-diag-1.pk 
b/testsuite/poke.pkl/asm-stmt-diag-1.pk
new file mode 100644
index 00000000..7a89a254
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-diag-1.pk
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+
+asm ("":
+     1 + 2 /* { dg-error "l-value" } */
+    );
diff --git a/testsuite/poke.pkl/asm-stmt-diag-2.pk 
b/testsuite/poke.pkl/asm-stmt-diag-2.pk
new file mode 100644
index 00000000..b8a24969
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-diag-2.pk
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+
+var a = 10;
+
+asm ("":
+     a,
+     1 + 2 /* { dg-error "l-value" } */
+:
+    );
diff --git a/testsuite/poke.pkl/asm-stmt-diag-3.pk 
b/testsuite/poke.pkl/asm-stmt-diag-3.pk
new file mode 100644
index 00000000..f49056e0
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-diag-3.pk
@@ -0,0 +1,5 @@
+/* { dg-do compile } */
+
+asm (
+  10 /* { dg-error "expected string" } */
+);
diff --git a/testsuite/poke.pkl/asm-stmt-diag-4.pk 
b/testsuite/poke.pkl/asm-stmt-diag-4.pk
new file mode 100644
index 00000000..825da2e0
--- /dev/null
+++ b/testsuite/poke.pkl/asm-stmt-diag-4.pk
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+
+asm (
+  10 /* { dg-error "expected string" } */
+  : :
+);
-- 
2.30.2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]