From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail2-relais-roc.national.inria.fr (mail2-relais-roc.national.inria.fr [192.134.164.83]) by c5ff346549e7 (Postfix) with ESMTPS id 777975D5 for ; Tue, 22 Jun 2021 13:56:21 +0000 (UTC) X-IronPort-AV: E=Sophos;i="5.83,291,1616454000"; d="scan'208,217";a="516058735" Received: from prod-listesu18.inria.fr (HELO sympa.inria.fr) ([128.93.162.160]) by mail2-relais-roc.national.inria.fr with ESMTP; 22 Jun 2021 15:55:45 +0200 Received: by sympa.inria.fr (Postfix, from userid 20132) id 44BE4E035B; Tue, 22 Jun 2021 15:55:45 +0200 (CEST) Received: from mail3-relais-sop.national.inria.fr (mail3-relais-sop.national.inria.fr [192.134.164.104]) by sympa.inria.fr (Postfix) with ESMTPS id D545AE034C for ; Tue, 22 Jun 2021 15:55:41 +0200 (CEST) Authentication-Results: mail3-smtp-sop.national.inria.fr; spf=None smtp.pra=info@gerd-stolpmann.de; spf=None smtp.mailfrom=info@gerd-stolpmann.de; spf=None smtp.helo=postmaster@mout.kundenserver.de IronPort-PHdr: =?us-ascii?q?A9a23=3AyVRyQxUMLb/Q5kk9gp7jnljoHsbV8KyzUjF92vM?= =?us-ascii?q?cY1JmTK2v8tzYMVDF4r011RmVBNSdsK4cwLOM7ujJYi8p2d65qncMcZhBBVcuq?= =?us-ascii?q?P49uEgeOvODElDxN/XwbiY3T4xoXV5h+GynYwAOQJ6tL1LdrWev4jEMBx7xKRR?= =?us-ascii?q?6JvjvGo7Vks+7y/2+94fcbglVgDexe71/IRqqoQneq8Uan4tvIbstxxXUpXdFZ?= =?us-ascii?q?/5Yzn5yK1KJmBb86Maw/Jp9/ClVpvks6c1OX7jkcqohVbBXAygoPG4z5M3wqBn?= =?us-ascii?q?MVhCP6WcGUmUXiRVHHQ7I5wznU5jrsyv6su192DSGPcDzULs5Vyiu47ttRRT1j?= =?us-ascii?q?ioMKjw3/3zNisFokqxVoA+vqR9xzYHab46aKOFzfqzBcd4AQmRNQshcWi5HD4i?= =?us-ascii?q?hb4UPFe0BPeNAoof8uVQOtwaxDhSxCuPzzT9Igmf23agg3OQnFwHNwQstH9MIs?= =?us-ascii?q?HTaq9X4L6gSXv6vzKbV1DnDdO9W2TD56IjQdxAuu/eMXbRqfcXM10YiDgXIhUi?= =?us-ascii?q?fpoL5JT2azPgNs3SF4Op6U+Kik20qpx1vrjWyxskglJTEi54Lx13E+it0w4Y7K?= =?us-ascii?q?N+7RUJnfNKpEpVeuj2HO4dqTM4vQ39ktDg1x7AapJK1cygExpshyhXCZfKHdI2?= =?us-ascii?q?I7QjiVOaXOTp4i3NleK6/hxav6kes0PHzVs6x0FpSqypFk9bNtmwM1xzU7MiIU?= =?us-ascii?q?P998l281jmRzwzT8P9LIUU1lavUL54u2KU/loEJvUvfGS/2nV36g7OMeUUh/ui?= =?us-ascii?q?n9+XnYqnmp5OGMI90kA7+PrwhmsOhG+Q3LxECX3OH+eS70L3j5Uj5T69Mjv0wi?= =?us-ascii?q?KXWrY7VKMIGraC6Gw9Yypgv5wuhAzu8ztgUg3sKIEhYdB+FlYTlJl/DLOj7APq?= =?us-ascii?q?wmVigjStnyvLcMrH/HJnALWLPnbn/cbt79kVS0hA8zcpF6JJRErwBIOz8Wkv2t?= =?us-ascii?q?NHACx85NBG0w/r9BNV+y4MeX3+ADbGfMKPJr1CI/PwvLPeWZIMPpTnyNeAp5//?= =?us-ascii?q?ojXAnhV8QZbel0YYJZHyiAPhqPUeUbWDxjtoDC2sGowQzQPTviFKYUD5TY3iyX?= =?us-ascii?q?7g75jE+EI+mDIHDRo6qgLGa0ye0AIdWaX1fBlCXDXfocIGEW+8JaC2IJM9hlCY?= =?us-ascii?q?IWqW/RIM5zxGhqBf6y6Z7LurT4iAXqYjs1N1x5+HKkREy9Cd0D9iG3mGWT2B0m?= =?us-ascii?q?3sISCUs0KB+p0x90FaD3rJij/xWD9wAr89OBwwzMJqZy+1hF/jzXBjAd5GHUgW?= =?us-ascii?q?IWNKjVB48VN55+MMJZ154EtOkxkTC2SStK7AYjbDOH4Az9rrZ1n73YcpwnSWVn?= =?us-ascii?q?JI9hkUrF5McfVatgbRyolC772/hlkKEi+CpaKIQ0SjW+X2dwHDIsEwKCGaYtI3?= =?us-ascii?q?KUGoTI1DKqtDh40rESfmiBOZ/WuOu4cGPMKcPccHuiU1DTfHlftjTMTrZpg=3D?= =?us-ascii?q?=3D?= IronPort-HdrOrdr: =?us-ascii?q?A9a23=3AzjkJfay37GvviYrMjB5eKrPwFb1zdoMgy1kn?= =?us-ascii?q?xilNoH1uHfBw8vrEoB11726StN9vYhsdcLy7Scy9qBDnmKKdg7N8AV7KZmCPhI?= =?us-ascii?q?LCFvAB0WKN+UyFJwTOss1a3qdsGpIOa+EYdmIVsS8M2mmF+7tL+ri6zJw=3D?= X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: =?us-ascii?q?A0CLcwBe6tFghg0R49RQChoBAQEBATwBA?= =?us-ascii?q?QEBAgIBAQEBAgEBAQEDAQEBAYFugTwCAQEBAQGBXVg5MYRIiQSIRC0UfIxBj3k?= =?us-ascii?q?LAQMBCAUSAiEJAQIEAQGHPwIdBgY0EwIEFQEBBQEBAQIBAwMEARMBAQEBCwsLC?= =?us-ascii?q?CkahTsHJg2COCKEFTJzDgI/LQYCAQEFgmgBgwsBCqcogTKBAYNLAQMCgRWFEhC?= =?us-ascii?q?BOAIBAQEBAQEBgUyEGQpJSAGGYiccPwGBTYEVJwyBSAwcSoNLCwIBgTIEEYMvg?= =?us-ascii?q?mQEgy0aEzcbChEOAQFPAXQVgRuQaAcME6llgRWCA4EmKQaEcIMAgXWHMYksgnw?= =?us-ascii?q?GOYNekSc0kDsBhmKJT40Qe4EFBQmCKYVtjRMphSWBa4F9TSQUgyQJRxkOgyeLE?= =?us-ascii?q?YNXM4RhhUxAMQI2AgYKAQEDCVgBAQGJI4JHAQE?= X-IPAS-Result: =?us-ascii?q?A0CLcwBe6tFghg0R49RQChoBAQEBATwBAQEBAgIBAQEBAgE?= =?us-ascii?q?BAQEDAQEBAYFugTwCAQEBAQGBXVg5MYRIiQSIRC0UfIxBj3kLAQMBCAUSAiEJA?= =?us-ascii?q?QIEAQGHPwIdBgY0EwIEFQEBBQEBAQIBAwMEARMBAQEBCwsLCCkahTsHJg2COCK?= =?us-ascii?q?EFTJzDgI/LQYCAQEFgmgBgwsBCqcogTKBAYNLAQMCgRWFEhCBOAIBAQEBAQEBg?= =?us-ascii?q?UyEGQpJSAGGYiccPwGBTYEVJwyBSAwcSoNLCwIBgTIEEYMvgmQEgy0aEzcbChE?= =?us-ascii?q?OAQFPAXQVgRuQaAcME6llgRWCA4EmKQaEcIMAgXWHMYksgnwGOYNekSc0kDsBh?= =?us-ascii?q?mKJT40Qe4EFBQmCKYVtjRMphSWBa4F9TSQUgyQJRxkOgyeLEYNXM4RhhUxAMQI?= =?us-ascii?q?2AgYKAQEDCVgBAQGJI4JHAQE?= X-IronPort-AV: E=Sophos;i="5.83,291,1616454000"; d="scan'208,217";a="385813318" X-MGA-submission: =?us-ascii?q?MDEhdf0kAgOoSMK34eyIeyW05rYnjHrCqb7IXk?= =?us-ascii?q?fJ/nqwxc0kSyA1CxCUq6BsVrPJqwoTQD5rFGFRkcHerMHQFb8MuRtpCN?= =?us-ascii?q?rO9CxdN6/zbUaQ5fpNS2D2fDkZxyAq6dN6q+ZKPqDHIdgc3kdjTUbV0H?= =?us-ascii?q?NSyQFFtNPIFZYihkqtNJQnAA=3D=3D?= Received: from mout.kundenserver.de ([212.227.17.13]) by mail3-smtp-sop.national.inria.fr with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 22 Jun 2021 15:55:40 +0200 Received: from cyrus.sumadev.de ([85.183.69.161]) by mrelayeu.kundenserver.de (mreue106 [212.227.15.183]) with ESMTPSA (Nemesis) id 1Mlvr3-1lVOLh2aPL-00j2s6 for ; Tue, 22 Jun 2021 15:55:38 +0200 Received: from gerdpro.local (84-107-228-32.cable.dynamic.v4.ziggo.nl [84.107.228.32]) by cyrus.sumadev.de (Postfix) with ESMTPSA id 36D79201A3158 for ; Tue, 22 Jun 2021 13:55:38 +0000 (UTC) To: caml-list@inria.fr From: Gerd Stolpmann Message-ID: <2df12937-40cb-1766-73e8-c847311b7238@gerd-stolpmann.de> Date: Tue, 22 Jun 2021 15:55:32 +0200 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:78.0) Gecko/20100101 Thunderbird/78.11.0 MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha256; protocol="application/pgp-signature"; boundary="N1LvnsMig810cQKu9OYkBPLYC8u9yqK4w" X-Provags-ID: V03:K1:xMmat62TDQAvzAPXATyCQTfHwh5ZHq7CN4nv/WIcES1oMdQ0d7A tr3KNdGvaAJE1u0cp5fsG7POgmEZNQsLocOfwKCIlAr5mavPnB/dgWU4IooMc7+pEE2FczF c5oubNvgu94Sen6Ajzw2VCZOTZWHQ5sxzsJu+NQ9XjKmQ74ui/+u7MdTRd3yGnUZ3QJMlcj R/cnih5CybTQ5qlt55NrA== X-Spam-Flag: NO X-UI-Out-Filterresults: notjunk:1;V03:K0:Gy4GSlPUCOE=:ksD6TNMYPaly2SGTJdGdAp KiQ+IuDTXicgGE6I6727D9gmKQY7+3OFO3+KdewabmhIuuRz/98KD/QdSr9b13FS/8TKe7DkP hwGLoSZNOREHPXZ1QIFxOEoux9GqmFJ3ju6J/lu8ZeyrTae5oatfQSMf3YUHP7/oW0aTgPL7e zFzAKkFTNXDq9SC7nyZ6pmG0QIqyHSTIrw3imfT2TV3oWgCLHLIdjKgJw+7/qN+hZZf7SxQDB JBgDXVfr/CjLYBROBO72HmvtGSpUze3BWFbyeYxspVc5S8UWqUoVZfTiBeAzKpASb4IVFfrBh +SqMlq9gbrfDZr2b7wABrRYklfkgq0ROkKI73iDoG+A49WRJ6G1Y3gS8oS2er857FgX7wuXco weHJFHzI1cxFGFJZAStXtGKXzFk/QzPONFwGxmdcZIPh0sA3bJxUWKVQQ0HM2YK3SuYbSIySA Kf37ATbm0lBIVOHwQSXOxWEoOSvOst6447rfzd6AFINKqc4F6efSBPJNaX9TFM41CCEp1SKhO 1xhH3PVxbVhqpWSYcXD/hU= Subject: [Caml-list] [ANN] wasicaml - a code emitter for OCaml targeting WebAssembly Reply-To: Gerd Stolpmann X-Loop: caml-list@inria.fr X-Sequence: 18531 Errors-To: caml-list-owner@inria.fr Precedence: list Precedence: bulk Sender: caml-list-request@inria.fr X-no-archive: yes List-Id: List-Help: List-Subscribe: List-Unsubscribe: List-Post: List-Owner: List-Archive: Archived-At: This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --N1LvnsMig810cQKu9OYkBPLYC8u9yqK4w Content-Type: multipart/mixed; boundary="PYzt2votSFlrRoZ2abz7TjJy03mAaYTjG"; protected-headers="v1" From: Gerd Stolpmann To: caml-list@inria.fr Message-ID: <2df12937-40cb-1766-73e8-c847311b7238@gerd-stolpmann.de> Subject: [ANN] wasicaml - a code emitter for OCaml targeting WebAssembly --PYzt2votSFlrRoZ2abz7TjJy03mAaYTjG Content-Type: multipart/alternative; boundary="------------025CFC361B7994EC8313E470" This is a multi-part message in MIME format. --------------025CFC361B7994EC8313E470 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hello everybody, I'd like to announce a new project to develop a code generator that emits= WebAssembly: https://github.com/remixlabs/wasicaml With the support of RemixLabs I could already create a very first version= that takes the OCaml bytecode as input and translates it to WebAssembly. While this approach probably doesn't lead to the fastest code, it is easy to accomplish, and it demonstrates the challenge (and already shows = how to solve many of the part problems along the road). To be precisely, the target of the translator is wasm32-unknown-wasi, i.e= =2E the WASI ABI. This ABI is still in early development, but provides alread= y the syscalls (or better, host calls) to access files, to get the current time, and to read the environment. This is almost enough to run a compile= r - I only had to add system() so that ocamlc can start external preprocessor= s. Also, due to the fact that the current wasm implementations still lack exception handling, I had to assume the presence of a host emulation of exceptions (which is easy to provide if the host environment is Javascrip= t, but not necessarily for other environments). The translator takes the OCaml bytecode as input, i.e. you first create an excecutable $ ocamlc -o myexec ... and then make wasm out of it: $ wasicaml -o myexec.wasm myexec If you omit the .wasm suffix, wasicaml will put a preamble in front of th= e wasm code that starts the execution: $ wasicaml -o myexec_wasm myexec $ ./myexec_wasm Because of this trick, many problems of cross-compiling can be avoided. You may ask what the benefits of yet another "Web" language are. We alrea= dy have two emitters targeting Javascript - isn't that enough? Well, two answers here. First, WASI is a proper LLVM target. Because of this, you can link code from other languages with your executable (e.g. C or Rust). So you are not limited to OCaml but can use any language that also targets the WASI ABI. E.g. you can do $ wasicaml -o myexec.wasm myexec -ccopt -lfoo to also link in libfoo.a (which must also be compiled to wasm). So it is multi-lingual from the beginning. Second, WebAssembly can be used outside the web, too. WASI targets more the command-line, and server plugins, and generally any OS-independent environments. For example, imagine you have an Electron app with a great UI, but for some special functionality you need to include some OCaml code, too. You don't want to give up the OS-independence, and WASI gives you now a natural option to add the OCaml code. And you still have access to the filesystem without hassle. - Another example is edge computing, i.e. when the cloud is extended by computers outside the data center, and the code should be in a form so that it can be run on as many= platforms as possible. - All in all, WASI plays well when you need to combine OS-independence with a classic way of organizing the code as command or as server function, and you also need predictable performance.= The challenge of translating OCaml to wasm is mainly the garbage collecto= r. Wasm doesn't permit many of the tricks ocamlopt is using to know in which= memory (or register) locations OCaml values are stored. In wasm, there ar= e no registers but the closest vehicle are local variables. Now, it is not possible to scan these variables from the GC function, making it practica= lly impossible to put OCaml values there while a function is called that migh= t trigger a GC. There is also no really cheap way of obtaining a stack descriptor. Wasicaml inherits the stack from the bytecode interpreter and uses it as its own shadow stack for OCaml values. As wasicaml bases on the bytecode representation of the code, the bytecode instructions already ensure that= values always live in this stack when the GC might run. Wasicaml addition= ally tries to identify values that don't need this special treatment (like int= s and bools) and that are preferably stored in local variables, giving the wasm executor freedom to put these into registers or other high-speed locations. (Unfortunately, most of the type information is already erased= in the bytecode, and this is definitely one of the deficiencies of the bytecode approach.) In order to maximize the performance, it is probably best to avoid the stack whenever possible. The current approach of transforming the bytecod= e hasn't brought to an end yet with respect to such optimizations. For example, there could be more analyses that figure out when GC runs are actually possible and when it is safe to use local variables. Another problem of the bytecode basis is that all function calls are indirect, preventing the wasm executor from inlining functions. As a project, I'd like to see wasicaml progressing in two directions. First, make the current approach as good as possible - although basing it on the bytecode representation has its downsides, it is easy to unders= tand and it is possible to figure out what the necessary ingredients for fast code are. Second, get an idea where a possible real wasm backend would fit into the OCaml compiler (maybe it is c-- but maybe this doesn't= give us much and you start better with lambda). Anyway, welcome to the new world of WebAssembly! Gerd -- PS. If you are interested in WebAssembly and like to work with me on anot= her Wasm port for some time, there is a position: *https://www.mixtional.de/recruiting/2021-01/index.html -- PPS. Wasicaml is a project of Figly, Inc., commonly known as RemixLabs, developing a reactive low-code and code collaboration platform. https://remixlabs.com/= * --=20 ------------------------------------------------------------ Gerd Stolpmann, Darmstadt, Germany gerd@gerd-stolpmann.de My OCaml site: http://www.camlcity.org Contact details: http://www.camlcity.org/contact.html Company homepage: http://www.gerd-stolpmann.de ------------------------------------------------------------ --------------025CFC361B7994EC8313E470 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
Hello everybody,
I'd like to announce a new project to develop a code generator t=
hat emits
WebAssembly:
https://github.com/remixlabs/wasicaml
With the support of RemixLabs I could already create a very firs=
t version
that takes the OCaml bytecode as input and translates it to WebAssembly.
While this approach probably doesn't lead to the fastest code, it is
easy to accomplish, and it demonstrates the challenge (and already shows =
how
to solve many of the part problems along the road).

To be precisely, the target of the translator is wasm32-unknown-wasi, i.e=
=2E
the WASI ABI. This ABI is still in early development, but provides alread=
y
the syscalls (or better, host calls) to access files, to get the current
time, and to read the environment. This is almost enough to run a compile=
r -
I only had to add system() so that ocamlc can start external preprocessor=
s.
Also, due to the fact that the current wasm implementations still lack
exception handling, I had to assume the presence of a host emulation of
exceptions (which is easy to provide if the host environment is Javascrip=
t,
but not necessarily for other environments).

The translator takes the OCaml bytecode as input, i.e. you first create
an excecutable

$ ocamlc -o myexec ...

and then make wasm out of it:

$ wasicaml -o myexec.wasm myexec

If you omit the .wasm suffix, wasicaml will put a preamble in front of th=
e
wasm code that starts the execution:

$ wasicaml -o myexec_wasm myexec
$ ./myexec_wasm

Because of this trick, many problems of cross-compiling can be avoided.

You may ask what the benefits of yet another "Web" language are. We alrea=
dy
have two emitters targeting Javascript - isn't that enough? Well, two
answers here.

First, WASI is a proper LLVM target. Because of this, you can link
code from other languages with your executable (e.g. C or Rust). So
you are not limited to OCaml but can use any language that also targets
the WASI ABI. E.g. you can do

$ wasicaml -o myexec.wasm myexec -ccopt -lfoo

to also link in libfoo.a (which must also be compiled to wasm). So
it is multi-lingual from the beginning.

Second, WebAssembly can be used outside the web, too. WASI targets more
the command-line, and server plugins, and generally any OS-independent
environments. For example, imagine you have an Electron app with a
great UI, but for some special functionality you need to include some
OCaml code, too. You don't want to give up the OS-independence, and
WASI gives you now a natural option to add the OCaml code. And you still
have access to the filesystem without hassle. - Another example is edge
computing, i.e. when the cloud is extended by computers outside the data
center, and the code should be in a form so that it can be run on as many=

platforms as possible. - All in all, WASI plays well when you need to
combine OS-independence with a classic way of organizing the code as
command or as server function, and you also need predictable performance.=


The challenge of translating OCaml to wasm is mainly the garbage collecto=
r.
Wasm doesn't permit many of the tricks ocamlopt is using to know in which=

memory (or register) locations OCaml values are stored. In wasm, there ar=
e
no registers but the closest vehicle are local variables. Now, it is not
possible to scan these variables from the GC function, making it practica=
lly
impossible to put OCaml values there while a function is called that migh=
t
trigger a GC. There is also no really cheap way of obtaining a stack
descriptor.

Wasicaml inherits the stack from the bytecode interpreter and uses it as
its own shadow stack for OCaml values. As wasicaml bases on the bytecode
representation of the code, the bytecode instructions already ensure that=

values always live in this stack when the GC might run. Wasicaml addition=
ally
tries to identify values that don't need this special treatment (like int=
s
and bools) and that are preferably stored in local variables, giving the
wasm executor freedom to put these into registers or other high-speed
locations. (Unfortunately, most of the type information is already erased=

in the bytecode, and this is definitely one of the deficiencies of the
bytecode approach.)

In order to maximize the performance, it is probably best to avoid the
stack whenever possible. The current approach of transforming the bytecod=
e
hasn't brought to an end yet with respect to such optimizations. For
example, there could be more analyses that figure out when GC runs are
actually possible and when it is safe to use local variables.

Another problem of the bytecode basis is that all function calls are
indirect, preventing the wasm executor from inlining functions.

As a project, I'd like to see wasicaml progressing in two directions.
First, make the current approach as good as possible - although basing
it on the bytecode representation has its downsides, it is easy to unders=
tand
and it is possible to figure out what the necessary ingredients for fast
code are. Second, get an idea where a possible real wasm backend
would fit into the OCaml compiler (maybe it is c-- but maybe this doesn't=

give us much and you start better with lambda).
Anyway, welcome to the new world of WebAssembly!

Gerd

--
PS. If you are interested in WebAssembly and like to work with me on anot=
her
Wasm port for some time, there is a position:
https://www.mixtional.de/recruiting/2021-01/=
index.html
--
PPS. Wasicaml is a project of Figly, Inc., commonly known as RemixLabs,
developing a reactive low-code and code collaboration platform.
https:=
//remixlabs.com/
--=20
------------------------------------------------------------
Gerd Stolpmann, Darmstadt, Germany    gerd@gerd-stolpmann.de
My OCaml site:          http://www.camlcity.org
Contact details:        http://www.camlcity.org/contact.html
Company homepage:       http://www.gerd-stolpmann.de
------------------------------------------------------------
--------------025CFC361B7994EC8313E470-- --PYzt2votSFlrRoZ2abz7TjJy03mAaYTjG-- --N1LvnsMig810cQKu9OYkBPLYC8u9yqK4w Content-Type: application/pgp-signature; name="OpenPGP_signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="OpenPGP_signature" -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEQfK+11RvSqqcheUIKfvtoS9ntQIFAmDR69QACgkQKfvtoS9n tQL8Ow//Ro9YSHxITYY+mAJMYwq6TckhZssHjl7JiypGOckznCRACjRspZs8rlp1 K8XQAIbY+ITjMf6NgH1Efp8NNbUxhBZTmxw5T9LLpLtLn2O7Z5ugtHSoDijpHQst eYFAhRvX00zQpDAHgIN1zbKRQUGF55yfa++jFEzbT7RhL5PKvxvFSVyQjox+EFsm BbG9mKM43NsWIMpuNCDCGSzQqyLMCwnPCNzVRHUv0RbqDZYvkuamrwZ9MM8/P3cR RHutsRRDDQoCxFv1oKXOpArLhYmUzG1n1iltDuTs4SPrNIB+canXxAdQSaLrGY7X louUZO7exqcT5L7bXQO6DCZS+UQ37PJdBrrL1rJ3NSZDC9mukhOSvVICZT4u8Bf4 kwvw/qI0frGvlsPjOVGJa3C3DaXlZumof+zzwVg9Gn4783FU6wIiyrW4feSv5WN3 x/l1HcKFGFmPV4PpeJ1jCiz/OnNFCRw/a8LOSaMx9b9rJY1/lh5wK7i752CxOIGq a9mjtTglbj/pVkZRDKQh9BbHCCG3PvkLLl/ImGm/FIM2sUzcLl/lcRXeFlyqLFOy HqFJct1a57dI3w7wp6X8NWIWUqMQmU5OH9FAyHyLya8eeIXjY6VBOqmmHQMXHZAg ErK6RL/kq/IDn9QKUCwT9Yx9JDMZR1ZCtYgIQgmWew6YaJ+1gkU= =IuT+ -----END PGP SIGNATURE----- --N1LvnsMig810cQKu9OYkBPLYC8u9yqK4w--