[vala/staging] codegen: Add support for async main



commit 45fe8523ad392f1aaa0f24d83b63e91774938775
Author: Princeton Ferro <princetonferro gmail com>
Date:   Thu Jan 13 02:40:18 2022 -0500

    codegen: Add support for async main
    
    If main() is async then setup a new GMainLoop and call _vala_main()
    asynchronously.
    
    Fixes https://gitlab.gnome.org/GNOME/vala/issues/1275

 codegen/valaccodeattribute.vala                    |   6 +-
 codegen/valaccodemethodmodule.vala                 |  76 +++++++++++-
 codegen/valagasyncmodule.vala                      |   4 +-
 tests/Makefile.am                                  |   3 +-
 .../asynchronous/method-main-async-void.c-expected | 114 +++++++++++++++++
 tests/asynchronous/method-main-async-void.vala     |   4 +
 tests/asynchronous/method-main-async.c-expected    | 138 +++++++++++++++++++++
 tests/asynchronous/method-main-async.vala          |  11 ++
 tests/semantic/method-main-async.test              |   5 -
 vala/valamethod.vala                               |   5 -
 10 files changed, 347 insertions(+), 19 deletions(-)
---
diff --git a/codegen/valaccodeattribute.vala b/codegen/valaccodeattribute.vala
index 8cf459107..bd4c6b29d 100644
--- a/codegen/valaccodeattribute.vala
+++ b/codegen/valaccodeattribute.vala
@@ -762,7 +762,11 @@ public class Vala.CCodeAttribute : AttributeCache {
                                }
                                if (sym.name == "main" && sym.parent_symbol.name == null) {
                                        // avoid conflict with generated main function
-                                       return "_vala_main";
+                                       if (m.coroutine) {
+                                               return "_vala_main_async";
+                                       } else {
+                                               return "_vala_main";
+                                       }
                                } else if (sym.name.has_prefix ("_")) {
                                        return "_%s%s".printf (get_ccode_lower_case_prefix 
(sym.parent_symbol), sym.name.substring (1));
                                } else {
diff --git a/codegen/valaccodemethodmodule.vala b/codegen/valaccodemethodmodule.vala
index ff6d679ac..9b9fd3690 100644
--- a/codegen/valaccodemethodmodule.vala
+++ b/codegen/valaccodemethodmodule.vala
@@ -840,6 +840,42 @@ public abstract class Vala.CCodeMethodModule : CCodeStructModule {
 
                if (m.entry_point) {
                        // m is possible entry point, add appropriate startup code
+
+                       if (m.coroutine) {
+                               // add callback for async main
+                               var asyncmain_callback = new CCodeFunction (real_name + "_callback");
+                               asyncmain_callback.add_parameter (new CCodeParameter ("source_object", 
"GObject*"));
+                               asyncmain_callback.add_parameter (new CCodeParameter ("res", 
"GAsyncResult*"));
+                               asyncmain_callback.add_parameter (new CCodeParameter ("user_data", 
"gpointer"));
+                               asyncmain_callback.modifiers |= CCodeModifiers.STATIC;
+
+                               push_function (asyncmain_callback);
+                               ccode.add_declaration ("GMainLoop*", new CCodeVariableDeclarator.zero 
("loop", new CCodeIdentifier ("user_data")));
+
+                               // get the return value
+                               var finish_call = new CCodeFunctionCall (new CCodeIdentifier 
(get_ccode_finish_real_name (m)));
+                               finish_call.add_argument (new CCodeIdentifier ("res"));
+                               if (m.return_type is VoidType) {
+                                       ccode.add_expression (finish_call);
+                               } else {
+                                       // save the return value
+                                       var asyncmain_result = new CCodeIdentifier (real_name + "_result");
+                                       var asyncmain_result_decl = new CCodeDeclaration (get_ccode_name 
(int_type));
+                                       asyncmain_result_decl.add_declarator (new CCodeVariableDeclarator 
(asyncmain_result.name));
+                                       asyncmain_result_decl.modifiers = CCodeModifiers.STATIC;
+                                       cfile.add_type_member_declaration (asyncmain_result_decl);
+                                       ccode.add_assignment (asyncmain_result, finish_call);
+                               }
+
+                               // quit the main loop
+                               var loop_quit_call = new CCodeFunctionCall (new CCodeIdentifier 
("g_main_loop_quit"));
+                               loop_quit_call.add_argument (new CCodeIdentifier ("loop"));
+                               ccode.add_expression (loop_quit_call);
+
+                               pop_function ();
+                               cfile.add_function (asyncmain_callback);
+                       }
+
                        var cmain = new CCodeFunction ("main", "int");
                        cmain.line = function.line;
                        cmain.add_parameter (new CCodeParameter ("argc", "int"));
@@ -855,17 +891,47 @@ public abstract class Vala.CCodeMethodModule : CCodeStructModule {
                                }
                        }
 
-                       var main_call = new CCodeFunctionCall (new CCodeIdentifier (function.name));
+                       var main_call = new CCodeFunctionCall (new CCodeIdentifier (m.coroutine ? real_name : 
function.name));
                        if (m.get_parameters ().size == 1) {
                                main_call.add_argument (new CCodeIdentifier ("argv"));
                                main_call.add_argument (new CCodeIdentifier ("argc"));
                        }
-                       if (m.return_type is VoidType) {
-                               // method returns void, always use 0 as exit code
+
+                       if (m.coroutine) {
+                               // main method is asynchronous, so we have to setup GMainLoop and run it
+                               var main_loop_new_call = new CCodeFunctionCall (new CCodeIdentifier 
("g_main_loop_new"));
+                               main_loop_new_call.add_argument (new CCodeConstantIdentifier ("NULL"));
+                               main_loop_new_call.add_argument (new CCodeConstantIdentifier ("FALSE"));
+                               ccode.add_declaration ("GMainLoop*", new CCodeVariableDeclarator.zero 
("loop", main_loop_new_call));
+
+                               // add some more arguments to main_call
+                               main_call.add_argument (new CCodeIdentifier (real_name + "_callback"));
+                               main_call.add_argument (new CCodeIdentifier ("loop"));
                                ccode.add_expression (main_call);
-                               ccode.add_return (new CCodeConstant ("0"));
+
+                               var main_loop_run_call = new CCodeFunctionCall (new CCodeIdentifier 
("g_main_loop_run"));
+                               main_loop_run_call.add_argument (new CCodeIdentifier ("loop"));
+                               ccode.add_expression (main_loop_run_call);
+
+                               var main_loop_unref_call = new CCodeFunctionCall (new CCodeIdentifier 
("g_main_loop_unref"));
+                               main_loop_unref_call.add_argument (new CCodeIdentifier ("loop"));
+                               ccode.add_expression (main_loop_unref_call);
+
+                               if (m.return_type is VoidType) {
+                                       // method returns void, always use 0 as exit code
+                                       ccode.add_return (new CCodeConstant ("0"));
+                               } else {
+                                       // get the saved return value
+                                       ccode.add_return (new CCodeIdentifier (real_name + "_result"));
+                               }
                        } else {
-                               ccode.add_return (main_call);
+                               if (m.return_type is VoidType) {
+                                       // method returns void, always use 0 as exit code
+                                       ccode.add_expression (main_call);
+                                       ccode.add_return (new CCodeConstant ("0"));
+                               } else {
+                                       ccode.add_return (main_call);
+                               }
                        }
                        pop_function ();
                        cfile.add_function (cmain);
diff --git a/codegen/valagasyncmodule.vala b/codegen/valagasyncmodule.vala
index 4f130946e..bbe810496 100644
--- a/codegen/valagasyncmodule.vala
+++ b/codegen/valagasyncmodule.vala
@@ -354,7 +354,7 @@ public class Vala.GAsyncModule : GtkModule {
                        var cparam_map = new HashMap<int,CCodeParameter> (direct_hash, direct_equal);
                        var carg_map = new HashMap<int,CCodeExpression> (direct_hash, direct_equal);
 
-                       if (m.is_private_symbol ()) {
+                       if (m.is_private_symbol () || m.entry_point) {
                                asyncfunc.modifiers |= CCodeModifiers.STATIC;
                        } else if (context.hide_internal && m.is_internal_symbol ()) {
                                asyncfunc.modifiers |= CCodeModifiers.INTERNAL;
@@ -374,7 +374,7 @@ public class Vala.GAsyncModule : GtkModule {
                        cparam_map = new HashMap<int,CCodeParameter> (direct_hash, direct_equal);
                        carg_map = new HashMap<int,CCodeExpression> (direct_hash, direct_equal);
 
-                       if (m.is_private_symbol ()) {
+                       if (m.is_private_symbol () || m.entry_point) {
                                finishfunc.modifiers |= CCodeModifiers.STATIC;
                        } else if (context.hide_internal && m.is_internal_symbol ()) {
                                finishfunc.modifiers |= CCodeModifiers.INTERNAL;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 5156172d2..f95b6bfaa 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -731,6 +731,8 @@ TESTS = \
        asynchronous/constructor-argument-check.vala \
        asynchronous/finish-name.vala \
        asynchronous/generator.vala \
+       asynchronous/method-main-async.vala \
+       asynchronous/method-main-async-void.vala \
        asynchronous/nowrapper.vala \
        asynchronous/out-parameter-free-on-error.vala \
        asynchronous/out-parameter-invalid.test \
@@ -1139,7 +1141,6 @@ TESTS = \
        semantic/method-extern-virtual.test \
        semantic/method-interface-already-found.test \
        semantic/method-interface-not-found.test \
-       semantic/method-main-async.test \
        semantic/method-main-inline.test \
        semantic/method-main-throws.test \
        semantic/method-override.test \
diff --git a/tests/asynchronous/method-main-async-void.c-expected 
b/tests/asynchronous/method-main-async-void.c-expected
new file mode 100644
index 000000000..c0733f84e
--- /dev/null
+++ b/tests/asynchronous/method-main-async-void.c-expected
@@ -0,0 +1,114 @@
+/* asynchronous_method_main_async_void.c generated by valac, the Vala compiler
+ * generated from asynchronous_method_main_async_void.vala, do not modify */
+
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+typedef struct _ValaMainAsyncData ValaMainAsyncData;
+
+struct _ValaMainAsyncData {
+       int _state_;
+       GObject* _source_object_;
+       GAsyncResult* _res_;
+       GTask* _async_result;
+       gchar** args;
+       gint args_length1;
+};
+
+static void _vala_main_async_data_free (gpointer _data);
+static void _vala_main_async (gchar** args,
+                       gint args_length1,
+                       GAsyncReadyCallback _callback_,
+                       gpointer _user_data_);
+static void _vala_main_finish (GAsyncResult* _res_);
+static gboolean _vala_main_async_co (ValaMainAsyncData* _data_);
+static gboolean __vala_main_async_co_gsource_func (gpointer self);
+
+static void
+_vala_main_async_data_free (gpointer _data)
+{
+       ValaMainAsyncData* _data_;
+       _data_ = _data;
+       g_slice_free (ValaMainAsyncData, _data_);
+}
+
+void
+_vala_main_async (gchar** args,
+                  gint args_length1,
+                  GAsyncReadyCallback _callback_,
+                  gpointer _user_data_)
+{
+       ValaMainAsyncData* _data_;
+       _data_ = g_slice_new0 (ValaMainAsyncData);
+       _data_->_async_result = g_task_new (NULL, NULL, _callback_, _user_data_);
+       g_task_set_task_data (_data_->_async_result, _data_, _vala_main_async_data_free);
+       _data_->args = args;
+       _data_->args_length1 = args_length1;
+       _vala_main_async_co (_data_);
+}
+
+void
+_vala_main_finish (GAsyncResult* _res_)
+{
+       ValaMainAsyncData* _data_;
+       _data_ = g_task_propagate_pointer (G_TASK (_res_), NULL);
+}
+
+static gboolean
+__vala_main_async_co_gsource_func (gpointer self)
+{
+       gboolean result;
+       result = _vala_main_async_co (self);
+       return result;
+}
+
+static gboolean
+_vala_main_async_co (ValaMainAsyncData* _data_)
+{
+       switch (_data_->_state_) {
+               case 0:
+               goto _state_0;
+               case 1:
+               goto _state_1;
+               default:
+               g_assert_not_reached ();
+       }
+       _state_0:
+       g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, __vala_main_async_co_gsource_func, _data_, NULL);
+       _data_->_state_ = 1;
+       return FALSE;
+       _state_1:
+       ;
+       g_task_return_pointer (_data_->_async_result, _data_, NULL);
+       if (_data_->_state_ != 0) {
+               while (!g_task_get_completed (_data_->_async_result)) {
+                       g_main_context_iteration (g_task_get_context (_data_->_async_result), TRUE);
+               }
+       }
+       g_object_unref (_data_->_async_result);
+       return FALSE;
+}
+
+static void
+_vala_main_async_callback (GObject* source_object,
+                           GAsyncResult* res,
+                           gpointer user_data)
+{
+       GMainLoop* loop = user_data;
+       _vala_main_finish (res);
+       g_main_loop_quit (loop);
+}
+
+int
+main (int argc,
+      char ** argv)
+{
+       GMainLoop* loop = g_main_loop_new (NULL, FALSE);
+       _vala_main_async (argv, argc, _vala_main_async_callback, loop);
+       g_main_loop_run (loop);
+       g_main_loop_unref (loop);
+       return 0;
+}
+
diff --git a/tests/asynchronous/method-main-async-void.vala b/tests/asynchronous/method-main-async-void.vala
new file mode 100644
index 000000000..0ce4e822d
--- /dev/null
+++ b/tests/asynchronous/method-main-async-void.vala
@@ -0,0 +1,4 @@
+async void main (string[] args) {
+       Idle.add (main.callback);
+       yield;
+}
diff --git a/tests/asynchronous/method-main-async.c-expected b/tests/asynchronous/method-main-async.c-expected
new file mode 100644
index 000000000..968f8eef4
--- /dev/null
+++ b/tests/asynchronous/method-main-async.c-expected
@@ -0,0 +1,138 @@
+/* asynchronous_method_main_async.c generated by valac, the Vala compiler
+ * generated from asynchronous_method_main_async.vala, do not modify */
+
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define _g_free0(var) (var = (g_free (var), NULL))
+typedef struct _ValaMainAsyncData ValaMainAsyncData;
+#define _vala_assert(expr, msg) if G_LIKELY (expr) ; else g_assertion_message_expr (G_LOG_DOMAIN, __FILE__, 
__LINE__, G_STRFUNC, msg);
+#define _vala_return_if_fail(expr, msg) if G_LIKELY (expr) ; else { g_return_if_fail_warning (G_LOG_DOMAIN, 
G_STRFUNC, msg); return; }
+#define _vala_return_val_if_fail(expr, msg, val) if G_LIKELY (expr) ; else { g_return_if_fail_warning 
(G_LOG_DOMAIN, G_STRFUNC, msg); return val; }
+#define _vala_warn_if_fail(expr, msg) if G_LIKELY (expr) ; else g_warn_message (G_LOG_DOMAIN, __FILE__, 
__LINE__, G_STRFUNC, msg);
+
+struct _ValaMainAsyncData {
+       int _state_;
+       GObject* _source_object_;
+       GAsyncResult* _res_;
+       GTask* _async_result;
+       gchar** args;
+       gint args_length1;
+       gint result;
+       const gchar* _tmp0_;
+       gchar* foo;
+       const gchar* _tmp1_;
+       gchar* _tmp2_;
+};
+
+static gint _vala_main_async_result;
+
+static void _vala_main_async_data_free (gpointer _data);
+static void _vala_main_async (gchar** args,
+                       gint args_length1,
+                       GAsyncReadyCallback _callback_,
+                       gpointer _user_data_);
+static gint _vala_main_finish (GAsyncResult* _res_);
+static gboolean _vala_main_async_co (ValaMainAsyncData* _data_);
+static gboolean __vala_main_async_co_gsource_func (gpointer self);
+
+static void
+_vala_main_async_data_free (gpointer _data)
+{
+       ValaMainAsyncData* _data_;
+       _data_ = _data;
+       g_slice_free (ValaMainAsyncData, _data_);
+}
+
+void
+_vala_main_async (gchar** args,
+                  gint args_length1,
+                  GAsyncReadyCallback _callback_,
+                  gpointer _user_data_)
+{
+       ValaMainAsyncData* _data_;
+       _data_ = g_slice_new0 (ValaMainAsyncData);
+       _data_->_async_result = g_task_new (NULL, NULL, _callback_, _user_data_);
+       g_task_set_task_data (_data_->_async_result, _data_, _vala_main_async_data_free);
+       _data_->args = args;
+       _data_->args_length1 = args_length1;
+       _vala_main_async_co (_data_);
+}
+
+gint
+_vala_main_finish (GAsyncResult* _res_)
+{
+       gint result;
+       ValaMainAsyncData* _data_;
+       _data_ = g_task_propagate_pointer (G_TASK (_res_), NULL);
+       result = _data_->result;
+       return result;
+}
+
+static gboolean
+__vala_main_async_co_gsource_func (gpointer self)
+{
+       gboolean result;
+       result = _vala_main_async_co (self);
+       return result;
+}
+
+static gboolean
+_vala_main_async_co (ValaMainAsyncData* _data_)
+{
+       switch (_data_->_state_) {
+               case 0:
+               goto _state_0;
+               case 1:
+               goto _state_1;
+               default:
+               g_assert_not_reached ();
+       }
+       _state_0:
+       _vala_assert (_data_->args_length1 >= 1, "args.length >= 1");
+       _data_->_tmp0_ = _data_->args[0];
+       _vala_assert (_data_->_tmp0_ != NULL, "args[0] != null");
+       _data_->_tmp1_ = _data_->args[0];
+       _data_->_tmp2_ = g_strdup (_data_->_tmp1_);
+       _data_->foo = _data_->_tmp2_;
+       g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, __vala_main_async_co_gsource_func, _data_, NULL);
+       _data_->_state_ = 1;
+       return FALSE;
+       _state_1:
+       ;
+       _vala_assert (g_strcmp0 (_data_->foo, "./asynchronous_method_main_async") == 0, "foo == 
\"./asynchronous_method_main_async\"");
+       _data_->result = 0;
+       _g_free0 (_data_->foo);
+       g_task_return_pointer (_data_->_async_result, _data_, NULL);
+       if (_data_->_state_ != 0) {
+               while (!g_task_get_completed (_data_->_async_result)) {
+                       g_main_context_iteration (g_task_get_context (_data_->_async_result), TRUE);
+               }
+       }
+       g_object_unref (_data_->_async_result);
+       return FALSE;
+}
+
+static void
+_vala_main_async_callback (GObject* source_object,
+                           GAsyncResult* res,
+                           gpointer user_data)
+{
+       GMainLoop* loop = user_data;
+       _vala_main_async_result = _vala_main_finish (res);
+       g_main_loop_quit (loop);
+}
+
+int
+main (int argc,
+      char ** argv)
+{
+       GMainLoop* loop = g_main_loop_new (NULL, FALSE);
+       _vala_main_async (argv, argc, _vala_main_async_callback, loop);
+       g_main_loop_run (loop);
+       g_main_loop_unref (loop);
+       return _vala_main_async_result;
+}
+
diff --git a/tests/asynchronous/method-main-async.vala b/tests/asynchronous/method-main-async.vala
new file mode 100644
index 000000000..c28aa3506
--- /dev/null
+++ b/tests/asynchronous/method-main-async.vala
@@ -0,0 +1,11 @@
+async int main (string[] args) {
+       assert (args.length >= 1);
+       assert (args[0] != null);
+       var foo = args[0];
+
+       Idle.add (main.callback);
+       yield;
+
+       assert (foo == "./asynchronous_method_main_async");
+       return 0;
+}
diff --git a/vala/valamethod.vala b/vala/valamethod.vala
index 27e417291..53d5ce72d 100644
--- a/vala/valamethod.vala
+++ b/vala/valamethod.vala
@@ -1114,11 +1114,6 @@ public class Vala.Method : Subroutine, Callable {
                                error = true;
                                Report.error (source_reference, "\"main\" method cannot be inline");
                        }
-
-                       if (coroutine) {
-                               error = true;
-                               Report.error (source_reference, "\"main\" method cannot be async");
-                       }
                }
 
                if (get_attribute ("GtkCallback") != null) {


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