Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

khepri_fun: propagate argument annotations to callee in bs_no_start_match2 validation failures #63

Merged
merged 3 commits into from
Mar 21, 2022

Conversation

the-mikedavis
Copy link
Member

@the-mikedavis the-mikedavis commented Mar 15, 2022

This is a follow up for removing the hard-coded register FIXME (#49 (comment)). Sorry the title is so long and jargon-y 😅

A simplification of the problem is that function A calls function B calls function C. A passes an argument to B that accepts_match_context, but B doesn't have any annotations for its parameters, so the beam_validator emits an error on the call instruction saying: no_bs_start_match2.

I got a bit derailed looking into this by function C...

In the test case fixtures, C has a parameter that accepts_match_context, so I thought that in order to fix this, we would need to determine the types of each function in the call graph (which naively seems easy based on beam_validator:build_function_table/2). It turns out though that this is not the case (see the commit message for edf1e98 for some more details on this).


In order to handle this particular beam_validator failure, we detect the annotations within the branch of the failed call instruction and add those annotions to the callee right after its entrypoint.

@coveralls
Copy link

coveralls commented Mar 15, 2022

Pull Request Test Coverage Report for Build 419

  • 43 of 44 (97.73%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.4%) to 85.699%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/khepri_fun.erl 43 44 97.73%
Totals Coverage Status
Change from base Build 409: 0.4%
Covered Lines: 1582
Relevant Lines: 1846

💛 - Coveralls

Copy link
Member

@dumbbell dumbbell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much! Great patch with detailed commit messages! This is really appreciated.

I made a few comments, mostly nitpicking :-)

src/khepri_fun.erl Outdated Show resolved Hide resolved
src/khepri_fun.erl Outdated Show resolved Hide resolved
src/khepri_fun.erl Outdated Show resolved Hide resolved
src/khepri_fun.erl Outdated Show resolved Hide resolved
@dumbbell dumbbell self-assigned this Mar 18, 2022
@the-mikedavis the-mikedavis force-pushed the md-propagate-comments-forward branch from 772c633 to ec87b2d Compare March 18, 2022 13:56
These test case functions are almost the same as the original
parse_float and parse_digits functions, except that they introduce
extra intermediary calls of parse_digits2 with additional bindings
prepended to the parameter list. These new bindings throw off the
arity of the bitstring match parameter `Bin` for which the original
validation fix tested by `allowed_bs_match_digit_parser_test` was
hard-coded.

These test cases also reveal some interesting behavior when we play
with the current code for fixing this validation error. This is the
original code which hard-codes a fix for a 1-arity function missing
the annotation on register {x,0} (the only argument):

    handle_validation_error(
      Asm,
      {_FailingFun,
       {{Call, 1 = _Arity, {f, EntryLabel}},
        _,
        no_bs_start_match2}},
      Error) when Call =:= call orelse Call =:= call_only ->
        Register = {x, 0},
        Comment = {'%', {var_info, Register, [accepts_match_context]}},
        Location = {'after', {label, EntryLabel}},
        add_comment_and_retry(Asm, Error, EntryLabel, Location, Comment);

Let's add a clause for a 2-arity function and hard-code the register
to {x,1}:

    handle_validation_error(
      Asm,
      {_FailingFun,
       {{Call, 2 = _Arity, {f, EntryLabel}},
        _,
        no_bs_start_match2}},
      Error) when Call =:= call orelse Call =:= call_only ->
        Register = {x, 1},
        Comment = {'%', {var_info, Register, [accepts_match_context]}},
        Location = {'after', {label, EntryLabel}},
        add_comment_and_retry(Asm, Error, EntryLabel, Location, Comment);

Initially, I expected this case to cover the new `parse_digits2/2` but
not `parse_digits2/3`. But In fact, `parse_digits2/3` needs no
annotations. This means that in order to fix this case, we don't need
to reach into the call-graph of `parse_digits2` and determine the
typings of the functions it calls. Instead we need to propagate the
`accepts_match_context` typing from `parse_float2/1` (`parse_digit2`'s
only caller).

This is good news because the validation error data does not include
any information about the eventual call to `parse_digits2/4`. We would
need to reach down the entire call-graph from `parse_float2/1` to
determine the types. Instead, the validation error contains enough
context to add the necessary annotations without ambiguity (see the
next commit).
(See the parent commit for some a primer on the problem and test
cases.)

This commit is refactors a fix for a `beam_validator` failure we
are currently solving by hard-coding a register. In the test case:

    parse_float2(<<".", Rest/binary>>) ->
        parse_digits2([], Rest);
    parse_float2(Bin) -> {[], Bin}.

    parse_digits2(Foo, Bin) ->
        parse_digits2(Foo, [], Bin).

    parse_digits2(Foo, Bar, Bin) ->
        parse_digits2(Foo, Bar, Bin, []).

    parse_digits2(
      Foo, Bar, <<Digit/integer, Rest/binary>>, Acc)
      when is_integer(Digit) andalso Digit >= 48 andalso Digit =< 57 ->
        parse_digits2(Foo, Bar, Rest, [Digit | Acc]);
    parse_digits2(_Foo, _Bar, Rest, Acc) ->
        {lists:reverse(Acc), Rest}.

Prior to this commit, `beam_validator` gave the following validation
error:

    {{'kfun__tx_funs__-allowed_bs_match_digit_parser2_test/0-fun-0-__33998213',
      tx_funs__parse_float2,1},
      {{call_only,2,{f,7}},10,no_bs_start_match2}}}

Which happens because `parse_float2/1` calls `parse_digits2/2`
with an argument which is annotated as accepting a match context,
but the parameter in `parse_digits2/2` is not yet annotated as
accepting a match context.

In order to fix this scenario, we need to determine which parameter
of `parse_digits2/2` needs to accept a match context, since that
information is not available in the `beam_validator` failure message.

Lets look at the disasm for that function:

    {function,tx_funs__parse_float2,1,4,
     [{label,3},
      {line,
       [{location,
         "/home/michael/dev-tools/code/khepri/test/tx_funs.erl",
         279}]},
      {func_info,
       {atom,
        'kfun__tx_funs__-allowed_bs_match_digit_parser2_test/0-fun-0-__33998213'},
       {atom,tx_funs__parse_float2},
       1},
      {label,4},
      {'%',{var_info,{x,0},[accepts_match_context]}},
      {bs_start_match4,{atom,no_fail},1,{x,0},{x,0}},
      {test,bs_match_string,
       {f,5},
       [{x,0},8,{string,<<".">>}]},
      {move,{x,0},{x,1}},
      {move,nil,{x,0}},
      {call_only,2,{f,7}},
      {label,5},
      {bs_get_tail,{x,0},{x,0},1},
      {test_heap,3,1},
      {put_tuple2,{x,0},{list,[nil,{x,0}]}},
      return]}

We want to pull forward the annotation (5th instruction, 1-indexed)
for `{x,0}` to the definition of the function being called ({f,7}).
First we lookup the function code for the failing function by Name
and Arity (`find_comments_for_call/4`), then determine the typing
of the branch in which the call occurs: in this case by scanning the
instructions between {label,4} (the start of the branch) and
instruction 10 (the call_only; we get this index from the
`beam_validator` failure message), gathering annotations and
following {move,Src,Dst} instructions.

Once we have the annotations leading up to the call, we can add
them to right after the callee's entrypoint. The `merge_comments/2`
and `add_comments_to_code/4` functions have been refactored to
allow merging in multiple comments at once, which may be necessary
if we're propagating the typing for multiple parameters.
This function is useful for finding a function by name and arity
(if defined) in a list of functions. We get a list of functions
either from the asm of the module being generated or from the
disasm from an existing module being disassembled, so this function
can be used by both `handle_validation_error/3' and `lookup_function/4`.
@the-mikedavis the-mikedavis force-pushed the md-propagate-comments-forward branch from 1c28f1b to 0821c6d Compare March 18, 2022 21:26
@dumbbell dumbbell merged commit ae57aab into rabbitmq:main Mar 21, 2022
@the-mikedavis the-mikedavis deleted the md-propagate-comments-forward branch March 21, 2022 12:12
@dumbbell dumbbell added the enhancement New feature or request label Mar 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants