From bb8aac44dd3dcd97fcf1d4f114a8d952cbba8b80 Mon Sep 17 00:00:00 2001 From: Benoit Vey Date: Fri, 3 Mar 2017 18:24:55 +0100 Subject: [PATCH] Really always call finalisers for embedded fields This fixes a case overlooked in 087ebc7. In that commit, the finaliser of an embedded field was only called if the parent object (or actor) had an explicit finaliser. This change adds implicit finalisers to objects that must finalise an embedded field, including recursively. --- Makefile | 2 + src/libponyc/codegen/genfun.c | 6 +- src/libponyc/reach/reach.c | 118 ++++++++++++++++++++++++++------- test/libponyc/codegen_final.cc | 106 +++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 26 deletions(-) create mode 100644 test/libponyc/codegen_final.cc diff --git a/Makefile b/Makefile index 0b21023f71..86f118e4ee 100644 --- a/Makefile +++ b/Makefile @@ -383,6 +383,8 @@ endif # target specific build options libponyrt.buildoptions = -DPONY_NO_ASSERT +libponyrt.tests.linkoptions += -rdynamic + libponyc.buildoptions = -D__STDC_CONSTANT_MACROS libponyc.buildoptions += -D__STDC_FORMAT_MACROS libponyc.buildoptions += -D__STDC_LIMIT_MACROS diff --git a/src/libponyc/codegen/genfun.c b/src/libponyc/codegen/genfun.c index d432c7133c..ab9feda79f 100644 --- a/src/libponyc/codegen/genfun.c +++ b/src/libponyc/codegen/genfun.c @@ -294,7 +294,7 @@ static void add_dispatch_case(compile_t* c, reach_type_t* t, ast_t* params, } static void call_embed_finalisers(compile_t* c, reach_type_t* t, - LLVMValueRef obj) + ast_t* method_body, LLVMValueRef obj) { uint32_t base = 0; if(t->underlying != TK_STRUCT) @@ -314,7 +314,9 @@ static void call_embed_finalisers(compile_t* c, reach_type_t* t, continue; LLVMValueRef field_ref = LLVMBuildStructGEP(c->builder, obj, base + i, ""); + codegen_debugloc(c, method_body); LLVMBuildCall(c->builder, final_fn, &field_ref, 1, ""); + codegen_debugloc(c, NULL); } } @@ -329,7 +331,7 @@ static bool genfun_fun(compile_t* c, reach_type_t* t, reach_method_t* m) name_params(c, t, m, params, m->func); if(m->func == t->final_fn) - call_embed_finalisers(c, t, gen_this(c, NULL)); + call_embed_finalisers(c, t, body, gen_this(c, NULL)); LLVMValueRef value = gen_expr(c, body); diff --git a/src/libponyc/reach/reach.c b/src/libponyc/reach/reach.c index dec8d7688b..750fd6c510 100644 --- a/src/libponyc/reach/reach.c +++ b/src/libponyc/reach/reach.c @@ -1,4 +1,5 @@ #include "reach.h" +#include "../ast/astbuild.h" #include "../codegen/genname.h" #include "../pass/expr.h" #include "../type/assemble.h" @@ -455,6 +456,89 @@ static void add_traits_to_type(reach_t* r, reach_type_t* t, } } +static void add_special(reach_t* r, reach_type_t* t, ast_t* type, + const char* special, pass_opt_t* opt) +{ + special = stringtab(special); + ast_t* find = lookup_try(NULL, NULL, type, special); + + if(find != NULL) + { + switch(ast_id(find)) + { + case TK_NEW: + case TK_FUN: + case TK_BE: + { + reachable_method(r, t->ast, special, NULL, opt); + ast_free_unattached(find); + break; + } + + default: {} + } + } +} + +static void add_final(reach_t* r, reach_type_t* t, pass_opt_t* opt) +{ + ast_t* def = (ast_t*)ast_data(t->ast); + + BUILD(final_ast, def, + NODE(TK_FUN, AST_SCOPE + NODE(TK_BOX) + ID("_final") + NONE + NONE + NONE + NONE + NODE(TK_SEQ, NODE(TK_TRUE)) + NONE + NONE)); + + ast_append(ast_childidx(def, 4), final_ast); + ast_set(def, stringtab("_final"), final_ast, SYM_NONE, false); + bool pop = frame_push(&opt->check, def); + bool ok = ast_passes_subtree(&final_ast, opt, PASS_FINALISER); + pony_assert(ok); + (void)ok; + + if(pop) + frame_pop(&opt->check); + + add_special(r, t, t->ast, "_final", opt); +} + +static bool embed_has_finaliser(ast_t* ast, const char* str_final) +{ + switch(ast_id(ast)) + { + case TK_NOMINAL: + break; + + default: + return false; + } + + ast_t* def = (ast_t*)ast_data(ast); + if(ast_get(def, str_final, NULL) != NULL) + return true; + + ast_t* members = ast_childidx(def, 4); + ast_t* member = ast_child(members); + + while(member != NULL) + { + if((ast_id(member) == TK_EMBED) && + embed_has_finaliser(ast_type(member), str_final)) + return true; + + member = ast_sibling(member); + } + + return false; +} + static void add_fields(reach_t* r, reach_type_t* t, pass_opt_t* opt) { ast_t* def = (ast_t*)ast_data(t->ast); @@ -488,6 +572,10 @@ static void add_fields(reach_t* r, reach_type_t* t, pass_opt_t* opt) member = ast_child(members); size_t index = 0; + const char* str_final = stringtab("_final"); + bool has_finaliser = ast_get(def, str_final, NULL) != NULL; + bool needs_finaliser = false; + while(member != NULL) { switch(ast_id(member)) @@ -502,12 +590,15 @@ static void add_fields(reach_t* r, reach_type_t* t, pass_opt_t* opt) AST_GET_CHILDREN(r_member, name, type, init); - t->fields[index].embed = ast_id(member) == TK_EMBED; + bool embed = t->fields[index].embed = ast_id(member) == TK_EMBED; t->fields[index].ast = reify(ast_type(member), typeparams, typeargs, opt, true); ast_setpos(t->fields[index].ast, NULL, ast_line(name), ast_pos(name)); t->fields[index].type = add_type(r, type, opt); + if(embed && !has_finaliser && !needs_finaliser) + needs_finaliser = embed_has_finaliser(type, str_final); + if(r_member != member) ast_free_unattached(r_member); @@ -520,30 +611,9 @@ static void add_fields(reach_t* r, reach_type_t* t, pass_opt_t* opt) member = ast_sibling(member); } -} -static void add_special(reach_t* r, reach_type_t* t, ast_t* type, - const char* special, pass_opt_t* opt) -{ - special = stringtab(special); - ast_t* find = lookup_try(NULL, NULL, type, special); - - if(find != NULL) - { - switch(ast_id(find)) - { - case TK_NEW: - case TK_FUN: - case TK_BE: - { - reachable_method(r, t->ast, special, NULL, opt); - ast_free_unattached(find); - break; - } - - default: {} - } - } + if(!has_finaliser && needs_finaliser) + add_final(r, t, opt); } static reach_type_t* add_reach_type(reach_t* r, ast_t* type) diff --git a/test/libponyc/codegen_final.cc b/test/libponyc/codegen_final.cc new file mode 100644 index 0000000000..336053dcfe --- /dev/null +++ b/test/libponyc/codegen_final.cc @@ -0,0 +1,106 @@ +#include +#include + +#include "util.h" + +#define TEST_COMPILE(src) DO(test_compile(src, "ir")) + + +class CodegenFinalTest : public PassTest +{}; + + +TEST_F(CodegenFinalTest, PrimitiveInit) +{ + const char* src = + "primitive PrimitiveInit\n" + " fun _init() =>\n" + " @pony_exitcode[None](I32(1))\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " None"; + + TEST_COMPILE(src); + + int exit_code = 0; + ASSERT_TRUE(run_program(&exit_code)); + ASSERT_EQ(exit_code, 1); +} + + +TEST_F(CodegenFinalTest, PrimitiveFinal) +{ + const char* src = + "primitive PrimitiveFinal\n" + " fun _final() =>\n" + " @pony_exitcode[None](I32(1))\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " None"; + + TEST_COMPILE(src); + + int exit_code = 0; + ASSERT_TRUE(run_program(&exit_code)); + ASSERT_EQ(exit_code, 1); +} + + +TEST_F(CodegenFinalTest, ClassFinal) +{ + const char* src = + "class ClassFinal\n" + " fun _final() =>\n" + " @pony_exitcode[None](I32(1))\n" + + "actor Main\n" + " new create(env: Env) =>\n" + " ClassFinal"; + + TEST_COMPILE(src); + + int exit_code = 0; + ASSERT_TRUE(run_program(&exit_code)); + ASSERT_EQ(exit_code, 1); +} + + +TEST_F(CodegenFinalTest, EmbedFinal) +{ + const char* src = + "class EmbedFinal\n" + " fun _final() =>\n" + " @pony_exitcode[None](I32(1))\n" + + "actor Main\n" + " embed c: EmbedFinal = EmbedFinal\n" + + " new create(env: Env) =>\n" + " None"; + + TEST_COMPILE(src); + + int exit_code = 0; + ASSERT_TRUE(run_program(&exit_code)); + ASSERT_EQ(exit_code, 1); +} + + +TEST_F(CodegenFinalTest, ActorFinal) +{ + const char* src = + "actor Main\n" + " new create(env: Env) =>\n" + " None\n" + + " fun _final() =>\n" + " @pony_exitcode[None](I32(1))"; + + TEST_COMPILE(src); + + int exit_code = 0; + ASSERT_TRUE(run_program(&exit_code)); + ASSERT_EQ(exit_code, 1); +}