From ccbe370adde9b0452da66eb876df03d5fc79ecc6 Mon Sep 17 00:00:00 2001 From: Elizabeth Mattijsen Date: Thu, 8 Feb 2024 15:04:03 +0100 Subject: [PATCH] Streamline return value logic - make Binder.get_return_type return nqp::null on Mu - make p6typecheckrv op to just emit the value if type is nqp::null effectively bypassing all typechecks if --> Mu - streamline the raku-rv-typecheck dispatcher - more comments - fast-path handling of Nil / Failure being returned - fewer checks on paths that need runtime / definite checks / coercion --- src/Perl6/bootstrap.c/BOOTSTRAP.nqp | 7 +- src/vm/moar/Perl6/Ops.nqp | 43 ++-- src/vm/moar/dispatchers.nqp | 322 ++++++++++++++++------------ 3 files changed, 209 insertions(+), 163 deletions(-) diff --git a/src/Perl6/bootstrap.c/BOOTSTRAP.nqp b/src/Perl6/bootstrap.c/BOOTSTRAP.nqp index 0705f8953f7..b4c8222c334 100644 --- a/src/Perl6/bootstrap.c/BOOTSTRAP.nqp +++ b/src/Perl6/bootstrap.c/BOOTSTRAP.nqp @@ -1031,7 +1031,12 @@ my class Binder { } method get_return_type($code) { - nqp::getattr(nqp::getattr($code, Code, '$!signature'), Signature, '$!returns') + my $type := nqp::getattr( + nqp::getattr($code, Code, '$!signature'), Signature, '$!returns' + ); + nqp::eqaddr($type,Mu) || nqp::eqaddr($type,NQPMu) + ?? nqp::null + !! $type } #?endif diff --git a/src/vm/moar/Perl6/Ops.nqp b/src/vm/moar/Perl6/Ops.nqp index 5848c9175ee..5cf13484223 100644 --- a/src/vm/moar/Perl6/Ops.nqp +++ b/src/vm/moar/Perl6/Ops.nqp @@ -408,33 +408,28 @@ $ops.add_hll_op('Raku', 'p6box', -> $qastcomp, $op { }); $ops.add_hll_op('Raku', 'p6typecheckrv', -> $qastcomp, $op { if nqp::istype($op[1], QAST::WVal) { - my $type := &get_binder().get_return_type($op[1].value); - if nqp::isnull($type) || nqp::objprimspec(nqp::decont($type)) { - $qastcomp.as_mast($op[0]) + my $value := $op[0]; + my $type := &get_binder().get_return_type($op[1].value); + if nqp::isnull($type) { + $qastcomp.as_mast(QAST::Op.new(:op, $value)); + } + elsif nqp::objprimspec(nqp::decont($type)) { + $qastcomp.as_mast($value) } else { my $is_generic := $type.HOW.archetypes($type).generic; - my $type_ast; - if $is_generic { - $type_ast := - QAST::Op.new( - :op, - :name, - QAST::Op.new(:op, QAST::WVal.new(:value($type))), - QAST::WVal.new(:value($type)), - QAST::Op.new(:op) - ); - } - else { - $type_ast := QAST::WVal.new( :value($type) ); - } - $qastcomp.as_mast(QAST::Op.new( - :op('dispatch'), - QAST::SVal.new( :value('raku-rv-typecheck') ), - QAST::Op.new( :op('p6box'), $op[0] ), - $type_ast, - QAST::IVal.new(:value($is_generic)) - )) + $qastcomp.as_mast(QAST::Op.new(:op, + QAST::SVal.new( :value('raku-rv-typecheck') ), + QAST::Op.new( :op('p6box'), $value ), + $is_generic + ?? QAST::Op.new(:op, :name, + QAST::Op.new(:op, QAST::WVal.new(:value($type))), + QAST::WVal.new(:value($type)), + QAST::Op.new(:op) + ) + !! QAST::WVal.new(:value($type)), + QAST::IVal.new(:value($is_generic)) + )); } } else { diff --git a/src/vm/moar/dispatchers.nqp b/src/vm/moar/dispatchers.nqp index 1d6f14b31e0..da603f0d5f6 100644 --- a/src/vm/moar/dispatchers.nqp +++ b/src/vm/moar/dispatchers.nqp @@ -4247,7 +4247,7 @@ sub select-coercer($coercion, $value, :$with-runtime = 0) { if method-is-optimizable($method); } - # We can TargetType.new($value). + # We can TargetType.new($value) elsif nqp::defined( $method := nqp::tryfindmethod($nominal_target, 'new')) && (my @cands := method-cando($method, $nominal_target, $value)) { @@ -4268,6 +4268,9 @@ sub select-coercer($coercion, $value, :$with-runtime = 0) { } } } + + # Want to check with runtime, and we have a method so go for runtime + # coercion elsif $with-runtime && nqp::isconcrete($method) { $coercer := $coerce-runtime; } @@ -4410,108 +4413,145 @@ nqp::register('raku-coercion', -> $capture { } }); -# Return value type-check dispatcher. The first value is the return value, -# the second is the type that is expected, which may be a definiteness or -# coercion type. -sub return_error($got, $wanted) { +#- raku-rv-typecheck ----------------------------------------------------------- +# Typechecking return values + +# Helper sub to return a typecheck error +sub return_error($got, $expected) { Perl6::Metamodel::Configuration.throw_or_die( - 'X::TypeCheck::Return', - "Type check failed for return value; expected '" ~ - $wanted.HOW.name($wanted) ~ "' but got '" ~ - $got.HOW.name($got) ~ "'", - :$got, - :expected($wanted) + 'X::TypeCheck::Return', + "Type check failed for return value; expected '" + ~ $expected.HOW.name($expected) + ~ "' but got '" + ~ $got.HOW.name($got) + ~ "'", + :$got, :$expected ); } +# Error out if type object didn't match or is an instantiated value, +# otherwise return value my $check_type_typeobj := -> $ret, $type { !nqp::isconcrete($ret) && nqp::istype($ret, $type) - ?? $ret - !! return_error($ret, $type) + ?? $ret + !! return_error($ret, $type) } +# Error out if value didn't match or is a type object, otherwise return value my $check_type_concrete := -> $ret, $type { nqp::isconcrete($ret) && nqp::istype($ret, $type) ?? $ret !! return_error($ret, $type) } +# Error out if type doesn't match, otherwise return value my $check_type := -> $ret, $type { - nqp::istype($ret, $type) - ?? $ret - !! return_error($ret, $type) + nqp::istype($ret, $type) ?? $ret !! return_error($ret, $type) } -# For @cdesc values see select-coercer sub. +# Error out if a value or it is the wrong type, or coercion didn't work +# out. For @cdesc values see select-coercer sub. my $check_type_typeobj_coerce := -> $ret, $type, $coercion, @cdesc { !nqp::isconcrete($ret) && nqp::istype($ret, $type) - ?? @cdesc[0]( # coercer code - $coercion, $ret, - @cdesc[1], # coercing method - @cdesc[2], # nominal target - nqp::how($coercion).target_type($coercion)) - !! return_error($ret, $type) + ?? nqp::atpos(@cdesc,0)( # coercer code + $coercion, + $ret, + nqp::atpos(@cdesc,1), # coercing method + nqp::atpos(@cdesc,2), # nominal target + nqp::how($coercion).target_type($coercion) + ) + !! return_error($ret, $type) } +# Error out if a type object or it is the wrong type, or coercion didn't work +# out. For @cdesc values see select-coercer sub. my $check_type_concrete_coerce := -> $ret, $type, $coercion, @cdesc { nqp::isconcrete($ret) && nqp::istype($ret, $type) - ?? @cdesc[0]( - $coercion, $ret, - @cdesc[1], - @cdesc[2], - nqp::how($coercion).target_type($coercion)) - !! return_error($ret, $type) + ?? nqp::atpos(@cdesc,0)( # coercer code + $coercion, $ret, + nqp::atpos(@cdesc,1), # coercing method + nqp::atpos(@cdesc,2), # nominal_target + nqp::how($coercion).target_type($coercion) + ) + !! return_error($ret, $type) } +# Error out if it is the wrong type, or coercion didn't work out. For +# @cdesc values see select-coercer sub. my $check_type_coerce := -> $ret, $type, $coercion, @cdesc { nqp::istype($ret, $type) - ?? @cdesc[0]( - $coercion, $ret, - @cdesc[1], - @cdesc[2], - nqp::how($coercion).target_type($coercion)) - !! return_error($ret, $type) + ?? nqp::atpos(@cdesc,0)( # coercer code + $coercion, + $ret, + nqp::atpos(@cdesc,1), # coercing method + nqp::atpos(@cdesc,2), # nominal_target + nqp::how($coercion).target_type($coercion) + ) + !! return_error($ret, $type) } +# Return value type-check dispatcher. The first value is the return value, +# the second is the type that is expected, which may be a definiteness or +# coercion type. The third argument is a native integer flag indicating +# whether type is generic or not. nqp::register('raku-rv-typecheck', -> $capture { - # Dispatcher arguments: - # - return value - # - type - # - "is generic" flag - # If the type is Mu or unset, then nothing is needed except identity. - my $type := nqp::captureposarg($capture, 1); - my $is_generic := nqp::captureposarg_i($capture, 2); - - # If the type has been instantiated from a generic then we'd need to track over it too. - if $is_generic { - my $track-ret-type := nqp::track('arg', $capture, 1); - nqp::guard('type', $track-ret-type); - } - - # We will never need the $is_generic argument, but otherwise the capture is used to call checker subs where the - # third argument is not anticipated. - $capture := nqp::syscall('dispatcher-drop-arg', $capture, 2); - if nqp::isnull($type) || $type =:= Mu { - nqp::delegate('boot-value', - nqp::syscall('dispatcher-drop-arg', $capture, 1)) + # Preset most common case, which will return identity + my str $delegate := 'boot-value'; + + # Fast track Nil / Failures being returned + my $rv := nqp::captureposarg($capture, 0); + my $Tvalue := nqp::track( 'arg', $capture, 0); + if nqp::istype($rv, Nil) + && (!nqp::iscont($rv) || nqp::istype_nd($rv,Scalar)) { + nqp::guard('type', nqp::iscont($rv) + ?? nqp::track('attr', $Tvalue, Scalar, '$!value') + !! $Tvalue + ); } - # Otherwise, need to look at the type. + # Not a simple type-checking bypass case else { + + # Helper sub to indicate whether a runtime check is needed my sub runtime-only($t) { - return 0 if nqp::isnull($t); - nqp::isconcrete(nqp::how_nd($t).refinement($t)) - || nqp::how_nd(my $refinee := nqp::how_nd($t).refinee($t)).archetypes($refinee).nominalizable - && runtime-only(nqp::how_nd($refinee).wrappee-lookup($refinee, :subset)) + nqp::isnull($t) + ?? 0 + !! nqp::isconcrete(nqp::how_nd($t).refinement($t)) + || nqp::how_nd( + my $refinee := nqp::how_nd($t).refinee($t) + ).archetypes($refinee).nominalizable + && runtime-only( + nqp::how_nd($refinee).wrappee-lookup($refinee, :subset) + ) } - my $rv := nqp::captureposarg($capture, 0); - my int $definite-check := -1; - my $how := nqp::how_nd($type); + # If the type has been instantiated from a generic then we'd need to + # track over it too. + nqp::guard('type', nqp::track('arg', $capture, 1)) + if nqp::captureposarg_i($capture, 2); + + # We will never need the $is_generic argument, but otherwise the + # capture is used to call checker subs where the third argument is + # not anticipated. + $capture := nqp::syscall('dispatcher-drop-arg', $capture, 2); + + # Make sure we guard on on the value and concreteness, even if + # inside a container + if nqp::istype_nd($rv, Scalar) { + nqp::guard('type', $Tvalue); + $Tvalue := nqp::track('attr', $Tvalue, Scalar, '$!value'); + } + nqp::guard('type', $Tvalue); + + # Set up check flags + my $type := nqp::captureposarg($capture, 1); + my $how := nqp::how_nd($type); my $coercion-type; my $constraint-type := nqp::null(); - my $runtime-check := 0; # Is there a run-time constraint? + my int $definite-check := -1; # do we need to check on concreteness? + my int $runtime-check; # Is there a run-time constraint? + if $how.archetypes($type).nominalizable { $runtime-check := runtime-only($how.wrappee-lookup($type, :subset)); unless nqp::isnull($coercion-type := $how.wrappee-lookup($type, :coercion)) { @@ -4524,96 +4564,102 @@ nqp::register('raku-rv-typecheck', -> $capture { if $how.archetypes($type).definite { my $dtype := $how.wrappee($type, :definite); $definite-check := nqp::how_nd($dtype).definite($dtype); + nqp::guard('concreteness', $Tvalue); } } - my $track-value := nqp::track('arg', $capture, 0); - - if nqp::istype_nd($rv, Scalar) { - nqp::guard('type', $track-value); - $track-value := - nqp::track('attr', $track-value, Scalar, '$!value'); - } + my $need-coercion := $how.archetypes($type).coercive + && !nqp::istype( + $rv, + nqp::how_nd($coercion-type).target_type($coercion-type) + ); - nqp::guard('type', $track-value); - nqp::guard('concreteness', $track-value) if $definite-check != -1; + # Needs a runtime check + if $runtime-check { + $delegate := 'boot-code-constant'; # always need to run code - my $need-coercion := $how.archetypes($type).coercive - && !nqp::istype($rv, nqp::how_nd($coercion-type).target_type($coercion-type)); - my $is-Nil := nqp::istype($rv, Nil); + # The most expensive path: subset with constraints and coercion. + if $need-coercion { + # First, we re-consider defininite check because now we're + # going to use coercion constraint for that + $definite-check := + nqp::how_nd( + $constraint-type + ).archetypes($constraint-type).definite + ?? nqp::how_nd( + my $dt := nqp::how_nd($constraint-type).wrappee( + $constraint-type, :definite + ) + ).definite($dt) + !! -1; - if $is-Nil || !($runtime-check || $need-coercion) - { - # Static typecheck which would either result in identity or error. - if $is-Nil - || (nqp::istype($rv, $type) - && ($definite-check == 0 - ?? !nqp::isconcrete($rv) - !! $definite-check == 1 - ?? nqp::isconcrete($rv) - !! 1)) - { - nqp::delegate('boot-value', - nqp::syscall('dispatcher-drop-arg', $capture, 1)); + $capture := nqp::syscall('dispatcher-insert-arg-literal-obj', + nqp::syscall('dispatcher-insert-arg-literal-obj', + nqp::syscall('dispatcher-insert-arg-literal-obj', + $capture, 2, $coercion-type + ), 3, select-coercer($coercion-type, $rv, :with-runtime) + ), 0, $definite-check == 0 + ?? $check_type_typeobj_coerce + !! $definite-check == 1 + ?? $check_type_concrete_coerce + !! $check_type_coerce + ); } + + # Subset with run-time constraints, no coercion. else { - # Not passing the definedness check. - nqp::delegate('boot-code', - nqp::syscall('dispatcher-insert-arg-literal-obj', $capture, 0, &return_error)); - } - } - elsif $runtime-check && !$need-coercion { - # The case of subset with run-time constraints, no coercion. - my $checker := - $definite-check == 0 + $capture := nqp::syscall('dispatcher-insert-arg-literal-obj', + $capture, 0, $definite-check == 0 ?? $check_type_typeobj !! $definite-check == 1 - ?? $check_type_concrete - !! $check_type; - nqp::delegate('boot-code-constant', - nqp::syscall('dispatcher-insert-arg-literal-obj', $capture, 0, $checker)); + ?? $check_type_concrete + !! $check_type + ); + } } - elsif !$runtime-check && $need-coercion { - # Coercion only. Make sure we can coerce by matching the constraint type and then dispatch directly to - # the coercion dispatcher. - if nqp::eqaddr($constraint-type, Mu) || nqp::istype($rv, $constraint-type) { - # Coercion dispatcher uses reverse order of arguments, same as the metamodel method `coerce`. - my $coercion-capture := - nqp::syscall('dispatcher-insert-arg-literal-obj', - nqp::syscall('dispatcher-drop-arg', $capture, 1), - 0, $type); - nqp::delegate('raku-coercion', $coercion-capture); + + # No runtime check, but needs coercion + elsif $need-coercion { + # Make sure we can coerce by matching the constraint type and + # then dispatch directly to the coercion dispatcher. + if nqp::eqaddr($constraint-type, Mu) + || nqp::istype($rv, $constraint-type) { + # Coercion dispatcher uses reverse order of arguments, same + # as the metamodel method `coerce`. + $delegate := 'raku-coercion'; + $capture := nqp::syscall('dispatcher-insert-arg-literal-obj', + nqp::syscall('dispatcher-drop-arg', $capture, 1), 0, $type + ); } + + # Coercion is not possible. else { - # Coercion is not possible. - nqp::delegate('boot-code-constant', - nqp::syscall('dispatcher-insert-arg-literal-obj', $capture, 0, &return_error)); + $delegate := 'boot-code-constant'; + $capture := nqp::syscall('dispatcher-insert-arg-literal-obj', + $capture, 0, &return_error + ); } } + + # No runtime check, no coercion else { - # The most expensive path: subset with constraints and coercion. - # First, we re-consider defininite check because now we're going to use coercion constraint for that. - $definite-check := - nqp::how_nd($constraint-type).archetypes($constraint-type).definite - ?? nqp::how_nd( - my $dt := nqp::how_nd($constraint-type).wrappee($constraint-type, :definite) - ).definite($dt) - !! -1; - my $checker := - $definite-check == 0 - ?? $check_type_typeobj_coerce - !! $definite-check == 1 - ?? $check_type_concrete_coerce - !! $check_type_coerce; - my @cdesc := select-coercer($coercion-type, $rv, :with-runtime); - my $checker-capture := - nqp::syscall('dispatcher-insert-arg-literal-obj', - nqp::syscall('dispatcher-insert-arg-literal-obj', - nqp::syscall('dispatcher-insert-arg-literal-obj', $capture, - 2, $coercion-type), - 3, @cdesc), # coercer code - 0, $checker); - nqp::delegate('boot-code-constant', $checker-capture); + + # Not passing the definedness check, otherwise identity + unless nqp::istype($rv, $type) + && ($definite-check == 0 + ?? !nqp::isconcrete($rv) + !! $definite-check == 1 + ?? nqp::isconcrete($rv) + !! 1 + ) { + $delegate := 'boot-code-constant'; + $capture := nqp::syscall('dispatcher-insert-arg-literal-obj', + $capture, 0, &return_error + ); + } } } + + # Do the actual delegation + nqp::delegate($delegate, $capture); });