From ec352f7af6163e2a72111fb266ba2c53c19c7a41 Mon Sep 17 00:00:00 2001 From: PraneethMerugu Date: Wed, 9 Sep 2026 00:08:01 -0400 Subject: [PATCH 1/3] Keep canonical collection key loads at one comparison call site --- CONTRIBUTING.md | 8 ++ benchmark/collect_canonical_order.jl | 44 ++++++++ docs/src/api/localmath.md | 7 ++ src/execution/collect_physical_support.jl | 7 +- .../collect_canonical_order_contracts.jl | 103 ++++++++++++++++++ test/metal/collect_canonical_order.jl | 7 ++ test/metal/runtests.jl | 1 + test/runtests.jl | 1 + test/test_collect_canonical_order.jl | 5 + 9 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 benchmark/collect_canonical_order.jl create mode 100644 test/fixtures/collect_canonical_order_contracts.jl create mode 100644 test/metal/collect_canonical_order.jl create mode 100644 test/test_collect_canonical_order.jl diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0496d1..67c1de0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,6 +97,14 @@ Pointwise traversal and its control checks are owned by untouched backing storage, nonempty publication, and runtime-prefix diagnostics through the ordinary CPU and Metal inventories. +Canonical collection ordering is owned by +`src/execution/collect_physical_support.jl`. The shared +`test/fixtures/collect_canonical_order_contracts.jl` checks reverse tuple-key +ordering across workgroup boundaries, partial participation, empty gated +collections, and duplicate-identity failure atomicity on CPU and real Metal. +Run these checks with `--check-bounds=yes` when changing shared-memory sorting; +disabled bounds checks must not substitute for correct padded-lane behavior. + Focused commands shorten the edit loop; they are not a second test inventory or release gate. Before handoff, run the complete suite of every changed package. Add the integration suite when a package boundary, extension, SciML diff --git a/benchmark/collect_canonical_order.jl b/benchmark/collect_canonical_order.jl new file mode 100644 index 0000000..32911f7 --- /dev/null +++ b/benchmark/collect_canonical_order.jl @@ -0,0 +1,44 @@ +using Statistics +import KernelAbstractions +include("../test/fixtures/collect_canonical_order_contracts.jl") + +# Run in a normal package environment, or test/metal's environment with --metal. +# Preparation and warmup are excluded; these timings are not pass/fail thresholds. +backend = if "--metal" in ARGS + @eval import Metal + Metal.functional() || error("the selected Metal backend is not functional") + Metal.allowscalar(false) + Metal.MetalBackend() +else + KernelAbstractions.CPU() +end +function benchmark_collect_order(backend, count) + (; prepared, storage) = _prepare_canonical_order_fixture(backend, count) + submit() = wait( + LocalMath.execute!( + prepared; + parameters = (; enabled = true, odd_only = false, duplicate = false, tied_keys = false) + ) + ) + for _ in 1:3 + submit() + end + samples = map(1:9) do _ + elapsed = @elapsed for _ in 1:10 + submit() + end + elapsed / 10 + end + records = collect(LocalMath.Adapt.adapt(Array, storage.records)) + @assert Array(storage.count) == Int32[count] + @assert map(record -> record.slot, records) == Int32.(count:-1:1) + return println( + (; + count, minimum_seconds = minimum(samples), + median_seconds = median(samples), maximum_seconds = maximum(samples), + ) + ) +end + +println("LocalMath=", pathof(LocalMath), " backend=", typeof(backend)) +foreach(count -> benchmark_collect_order(backend, count), (256, 4096)) diff --git a/docs/src/api/localmath.md b/docs/src/api/localmath.md index 4d83ef8..79e4ea9 100644 --- a/docs/src/api/localmath.md +++ b/docs/src/api/localmath.md @@ -44,6 +44,13 @@ storage; and `allocate()` creates the exact bounded storage for a produced Collection. A caller-owned `StructArray` is borrowed unchanged. Allocating one copies its component arrays recursively and preserves the record layout. +`Collect(...; order=canonical_by(key, identity))` orders participating records +by their keys and identities, including tuple-valued keys. A closed participation +gate publishes an empty logical collection without rewriting backing records. +Duplicate canonical identities fail validation before changing the previously +published count or records. The ordinary CPU and Metal collection-order tests +exercise these behaviors across partial workgroups with bounds checks enabled. + ## Public surface Ordinary authoring exports only the mathematical and execution vocabulary: diff --git a/src/execution/collect_physical_support.jl b/src/execution/collect_physical_support.jl index 2aed981..bfa331f 100644 --- a/src/execution/collect_physical_support.jl +++ b/src/execution/collect_physical_support.jl @@ -206,8 +206,11 @@ end ascending = ((lane - 1) & sort_size) == 0 left = @inbounds local_order[lane] right = @inbounds local_order[other] - swap = ascending ? _compacted_ordinal_less(port, workspace, right, left) : - _compacted_ordinal_less(port, workspace, left, right) + # Keep one checked key-load call site in this shared-memory loop. + first_ordinal = ifelse(ascending, right, left) + second_ordinal = ifelse(ascending, left, right) + swap = _compacted_ordinal_less(port, workspace, + first_ordinal, second_ordinal) if swap @inbounds begin local_order[lane] = right diff --git a/test/fixtures/collect_canonical_order_contracts.jl b/test/fixtures/collect_canonical_order_contracts.jl new file mode 100644 index 0000000..5902d77 --- /dev/null +++ b/test/fixtures/collect_canonical_order_contracts.jl @@ -0,0 +1,103 @@ +using Test +import LocalMath + +struct OrderedCollectionNode end +struct OrderedCollectionRecord + slot::Int32 + key::Tuple{Int32, UInt32, UInt32, UInt32} + identity::Tuple{UInt32, Int32, UInt32, Int32} +end +struct OrderedCollectionEvaluator end +@inline function (::OrderedCollectionEvaluator)(item::Int32, reads, parameters) + odd_only, duplicate, tied_keys = parameters + slot = duplicate ? Int32(1) : item + record = OrderedCollectionRecord( + slot, + (tied_keys ? Int32(0) : -slot, UInt32(1), UInt32(0), UInt32(0)), + (UInt32(1), -slot, UInt32(0), Int32(1)) + ) + return (; records = LocalMath.CollectedValue(record, !odd_only || isodd(item))) +end + +function _prepare_canonical_order_fixture(backend, count) + source = LocalMath.Space(OrderedCollectionNode, count) + collection = LocalMath.Collection(OrderedCollectionRecord, count) + enabled = LocalMath.Parameter(:enabled, Bool) + odd_only = LocalMath.Parameter(:odd_only, Bool) + duplicate = LocalMath.Parameter(:duplicate, Bool) + tied_keys = LocalMath.Parameter(:tied_keys, Bool) + publication = LocalMath.Publication( + ( + LocalMath.CollectionPublication( + collection, LocalMath.PublicationValue(:records) + ), + ), + LocalMath.Collect( + OrderedCollectionRecord; maximum = 1, + order = LocalMath.canonical_by(:key, :identity) + ) + ) + stage = LocalMath.Stage( + source, NamedTuple(), (publication,), + LocalMath.Evaluator(OrderedCollectionEvaluator(), (odd_only, duplicate, tied_keys)), + LocalMath.Control(gate = enabled), + LocalMath.SourceOrigin(:canonical_collection, 1) + ) + law = LocalMath.LocalLaw( + stage; + parameters = LocalMath.ParameterSchema(odd_only, duplicate, tied_keys, enabled) + ) + storage = LocalMath.CompactedStorage( + backend, OrderedCollectionRecord, count; + source_items = count + ) + prepared = LocalMath.prepare(law, collection => storage; backend) + return (; prepared, storage) +end + +function _collect_canonical_order_contract(backend) + host_records(storage) = collect(LocalMath.Adapt.adapt(Array, storage.records)) + return @testset "canonical tuple-key collection" begin + for count in (1, 2, 255, 256, 257) + @testset "capacity $count" begin + (; prepared, storage) = _prepare_canonical_order_fixture(backend, count) + @test Array(storage.count) == Int32[0] + run(; enabled = true, odd_only = false, duplicate = false, tied_keys = false) = + wait(LocalMath.execute!(prepared; parameters = (; enabled, odd_only, duplicate, tied_keys))) + run() + @test Array(storage.count) == Int32[count] + @test map(record -> record.slot, host_records(storage)) == Int32.(count:-1:1) + run(; odd_only = true) + expected = reverse(filter(isodd, Int32.(1:count))) + @test Array(storage.count) == Int32[length(expected)] + @test map( + record -> record.slot, + host_records(storage)[1:length(expected)] + ) == expected + run(; tied_keys = true) + @test Array(storage.count) == Int32[count] + @test map(record -> record.slot, host_records(storage)) == Int32.(count:-1:1) + published = host_records(storage) + run(; enabled = false) + @test Array(storage.count) == Int32[0] + @test host_records(storage) == published + if count > 1 + run() + published = host_records(storage) + previous_count = Array(storage.count) + failure = try + run(; duplicate = true) + nothing + catch error + error + end + @test failure isa LocalMath.LocalMathValidationError + @test failure.contract === :runtime_stage_validation + @test failure.actual.failure_class === :duplicate_identity + @test Array(storage.count) == previous_count + @test host_records(storage) == published + end + end + end + end +end diff --git a/test/metal/collect_canonical_order.jl b/test/metal/collect_canonical_order.jl new file mode 100644 index 0000000..59f4f91 --- /dev/null +++ b/test/metal/collect_canonical_order.jl @@ -0,0 +1,7 @@ +using Test +import Metal +include("../fixtures/collect_canonical_order_contracts.jl") + +Metal.functional() || error("canonical collection checks require real Metal") +Metal.allowscalar(false) +_collect_canonical_order_contract(Metal.MetalBackend()) diff --git a/test/metal/runtests.jl b/test/metal/runtests.jl index fce920c..4dcf86f 100644 --- a/test/metal/runtests.jl +++ b/test/metal/runtests.jl @@ -16,6 +16,7 @@ const LOCALMATH_METAL_WITNESSES = ( "product_values.jl", "ordered_fold_control.jl", "empty_pointwise_domains.jl", + "collect_canonical_order.jl", ) @testset "LocalMath Metal runner inventory" begin diff --git a/test/runtests.jl b/test/runtests.jl index e8d0795..4f5bcda 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,6 +21,7 @@ const LOCALMATH_INCLUDED_TESTS = ( "test_candidate_grouping.jl", "test_collect_stage_model.jl", "test_collect_stage_execution.jl", + "test_collect_canonical_order.jl", "test_ordered_fold_stage_model.jl", "test_ordered_fold_stage_execution.jl", "test_ordered_fold_control.jl", diff --git a/test/test_collect_canonical_order.jl b/test/test_collect_canonical_order.jl new file mode 100644 index 0000000..34cff74 --- /dev/null +++ b/test/test_collect_canonical_order.jl @@ -0,0 +1,5 @@ +using Test +import KernelAbstractions +include("fixtures/collect_canonical_order_contracts.jl") + +_collect_canonical_order_contract(KernelAbstractions.CPU()) From 5ccb6a3c4328eafa5a7f8957a683b50f06a8f8a7 Mon Sep 17 00:00:00 2001 From: PraneethMerugu Date: Tue, 8 Sep 2026 23:35:23 -0400 Subject: [PATCH 2/3] Admit native trigonometric stage calls with method ownership checks --- CONTRIBUTING.md | 9 ++++ docs/src/learn/localmath-troubleshooting.md | 6 +++ .../stage_program_kernelabstractions.jl | 13 +++-- .../fixtures/trigonometric_stage_contracts.jl | 51 +++++++++++++++++++ test/metal/runtests.jl | 1 + test/metal/trigonometric_stages.jl | 2 + test/runtests.jl | 1 + test/test_stage_preparation.jl | 38 ++++++++++++++ test/test_trigonometric_stages.jl | 3 ++ 9 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/trigonometric_stage_contracts.jl create mode 100644 test/metal/trigonometric_stages.jl create mode 100644 test/test_trigonometric_stages.jl diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67c1de0..b8badc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,6 +105,15 @@ collections, and duplicate-identity failure atomicity on CPU and real Metal. Run these checks with `--check-bounds=yes` when changing shared-memory sorting; disabled bounds checks must not substitute for correct padded-lane behavior. +Closed callable admission is owned by +`src/execution/stage_program_kernelabstractions.jl`. Its narrowly enumerated +pure unary floating-point calls require a method owned by `Base.Math`, a +concrete `AbstractFloat` argument and the same return type. Extensions undergo +ordinary recursive effect analysis; this does not admit arbitrary foreign calls. +`test/fixtures/trigonometric_stage_contracts.jl` exercises real sine/cosine +publication through ordinary CPU and Metal stages, while +`test/test_stage_preparation.jl` retains unsafe-capture/access rejection tests. + Focused commands shorten the edit loop; they are not a second test inventory or release gate. Before handoff, run the complete suite of every changed package. Add the integration suite when a package boundary, extension, SciML diff --git a/docs/src/learn/localmath-troubleshooting.md b/docs/src/learn/localmath-troubleshooting.md index bb29d8c..7e6e32c 100644 --- a/docs/src/learn/localmath-troubleshooting.md +++ b/docs/src/learn/localmath-troubleshooting.md @@ -52,6 +52,12 @@ unsafe globals, foreign calls, dynamic dispatch, recursion, and unsupported method shapes are rejected. The error reports the callable purpose, analyzed signature, selected method when available, and a recovery hint. +Real scalar `sin` and `cos` are available in ordinary evaluators. Their +native floating-point methods use the same closed pure-call admission as +`log` and `sqrt`; this is not permission for arbitrary foreign calls or +side-effecting extensions of those functions. CPU/Metal comparisons use +numerical tolerances rather than promising identical transcendental bits. + ## Receipt or transaction failure `execute!` returns a logical `ExecutionReceipt`; call `wait` or `waitall` to diff --git a/src/execution/stage_program_kernelabstractions.jl b/src/execution/stage_program_kernelabstractions.jl index a8e6922..cd31b95 100644 --- a/src/execution/stage_program_kernelabstractions.jl +++ b/src/execution/stage_program_kernelabstractions.jl @@ -295,7 +295,7 @@ const _POINTWISE_FAILURE_ONLY_INVOKES = ( Base.error, Base.throw_boundserror, Core.throw_inexacterror, ) const _POINTWISE_PURE_UNARY_FLOAT_INVOKES = ( - Base.log, Base.sqrt, Base.cos, + Base.log, Base.sqrt, Base.sin, Base.cos, ) const _POINTWISE_CALL_DEPTH_LIMIT = 32 const _POINTWISE_AND_INT = getglobal(getglobal(Core, :Intrinsics), :and_int) @@ -379,8 +379,10 @@ function _pointwise_residual_invoke_safe( return false end selected === method_instance.def || return false - if any(candidate -> candidate === binding, - _POINTWISE_PURE_UNARY_FLOAT_INVOKES) + if selected.module === Base.Math && any( + candidate -> candidate === binding, + _POINTWISE_PURE_UNARY_FLOAT_INVOKES + ) length(signature.parameters) == 1 || return false argument_type = only(signature.parameters) argument_type isa DataType && @@ -500,6 +502,11 @@ function _pointwise_concrete_callable_invoke_safe( callable_type = first(spec.parameters) observed_type = _pointwise_operand_type(callee, analysis.typed_context) observed_type isa Type && observed_type <: callable_type || return false + if callee isa Function && Base.issingletontype(callable_type) + return _pointwise_residual_invoke_safe( + code_instance, callee, analysis, depth + ) + end return _pointwise_method_instance_safe(method_instance, analysis, depth) end diff --git a/test/fixtures/trigonometric_stage_contracts.jl b/test/fixtures/trigonometric_stage_contracts.jl new file mode 100644 index 0000000..525a0ce --- /dev/null +++ b/test/fixtures/trigonometric_stage_contracts.jl @@ -0,0 +1,51 @@ +using Test +import LocalMath +import KernelAbstractions + +struct TrigonometricStageDomain end +struct TrigonometricStageEvaluator{F} + operation::F +end +@inline function (evaluator::TrigonometricStageEvaluator)(item::Int32, reads, parameters) + angle = something(reads[1][1].value) + return (value = LocalMath.UniqueValue(evaluator.operation(angle)),) +end + +function trigonometric_stage_contracts(array_type, ::Type{T} = Float32) where {T} + return @testset "real trigonometric stage publication ($T)" begin + angles = T[-100, -3, -0.25, -0.0, 0, 0.25, 1, 3, 100] + space = LocalMath.Space(TrigonometricStageDomain, length(angles)) + input = LocalMath.Field(space, T) + output = LocalMath.Field(space, T) + relation = LocalMath.IdentityRelation(space) + for operation in (sin, cos) + stage = LocalMath.Stage( + space, (angle = LocalMath.Access(input, relation),), + ( + LocalMath.Publication( + (LocalMath.FieldPublication(output, relation, LocalMath.PublicationValue(:value)),), + LocalMath.Unique(T), + ), + ), + LocalMath.Evaluator(TrigonometricStageEvaluator(operation), ()), + LocalMath.Control(), + LocalMath.SourceOrigin(@__FILE__, @__LINE__; label = :trigonometric_publication), + ) + values = array_type(angles) + destination = array_type(fill(T(17), length(angles))) + prepared = LocalMath.prepare( + LocalMath.LocalLaw(stage), input => values, output => destination; + backend = KernelAbstractions.get_backend(values), + ) + @test Array(destination) == fill(T(17), length(angles)) + wait(LocalMath.execute!(prepared)) + # Higher-precision host arithmetic is a numerical oracle, not a + # second stage evaluator or a device bitwise-transcendental claim. + reference = setprecision(BigFloat, 128) do + T.(operation.(BigFloat.(angles))) + end + @test Array(destination) ≈ reference rtol = 4eps(T) atol = 4eps(T) + @test isequal(Array(values), angles) + end + end +end diff --git a/test/metal/runtests.jl b/test/metal/runtests.jl index 4dcf86f..3d070aa 100644 --- a/test/metal/runtests.jl +++ b/test/metal/runtests.jl @@ -17,6 +17,7 @@ const LOCALMATH_METAL_WITNESSES = ( "ordered_fold_control.jl", "empty_pointwise_domains.jl", "collect_canonical_order.jl", + "trigonometric_stages.jl", ) @testset "LocalMath Metal runner inventory" begin diff --git a/test/metal/trigonometric_stages.jl b/test/metal/trigonometric_stages.jl new file mode 100644 index 0000000..bf22f93 --- /dev/null +++ b/test/metal/trigonometric_stages.jl @@ -0,0 +1,2 @@ +include(joinpath(@__DIR__, "..", "fixtures", "trigonometric_stage_contracts.jl")) +trigonometric_stage_contracts(Metal.MtlArray, Float32) diff --git a/test/runtests.jl b/test/runtests.jl index 4f5bcda..1ecc964 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,6 +11,7 @@ const LOCALMATH_INCLUDED_TESTS = ( "test_stage_preparation.jl", "test_direct_pointwise_stage.jl", "test_empty_pointwise_domains.jl", + "test_trigonometric_stages.jl", "test_product_values.jl", "test_unique_stage.jl", "test_stage_program_lifecycle.jl", diff --git a/test/test_stage_preparation.jl b/test/test_stage_preparation.jl index cd31926..229998c 100644 --- a/test/test_stage_preparation.jl +++ b/test/test_stage_preparation.jl @@ -19,6 +19,19 @@ end struct PreparedStageNoInlineIncrement end Base.@noinline (::PreparedStageNoInlineIncrement)(value::Int32) = value + Int32(1) +struct PreparedStageUnsafeSineFloat <: AbstractFloat + value::Float32 +end +const PREPARED_STAGE_SINE_MUTATION = Ref(0) +Base.@noinline function Base.sin(value::PreparedStageUnsafeSineFloat) + PREPARED_STAGE_SINE_MUTATION[] += 1 + return value +end +prepared_stage_sine(value) = sin(value) +struct PreparedStageUnaryFloat{F} + operation::F +end +(evaluator::PreparedStageUnaryFloat)(value) = evaluator.operation(value) struct PreparedStageNoInlineCapture values::Vector{Int32} end @@ -115,6 +128,31 @@ end Tuple{Int32}, method_signature -> length(method_signature) == 2 ) @test !unsafe.qualified + + # A Base function name does not make user-defined methods pure, even + # when the argument is isbits and the return type is unchanged. + unsafe_sine = LMPRE._closed_callable_effect_analysis( + prepared_stage_sine, Tuple{PreparedStageUnsafeSineFloat}, + method_signature -> length(method_signature) == 2, + ) + @test !unsafe_sine.qualified + @test PREPARED_STAGE_SINE_MUTATION[] == 0 + unsafe_captured_sine = LMPRE._closed_callable_effect_analysis( + PreparedStageUnaryFloat(sin), Tuple{PreparedStageUnsafeSineFloat}, + method_signature -> length(method_signature) == 2, + ) + @test !unsafe_captured_sine.qualified + @test PREPARED_STAGE_SINE_MUTATION[] == 0 + + for operation in (sin, cos, log, sqrt), type in (Float16, Float32, Float64) + unary = LMPRE._closed_callable_effect_analysis( + PreparedStageUnaryFloat(operation), Tuple{type}, + method_signature -> length(method_signature) == 2, + ) + @test unary.qualified + @test unary.return_type === type + end + @test PREPARED_STAGE_SINE_MUTATION[] == 0 end @testset "descriptor-free prepared Stage ABI" begin diff --git a/test/test_trigonometric_stages.jl b/test/test_trigonometric_stages.jl new file mode 100644 index 0000000..7ade35d --- /dev/null +++ b/test/test_trigonometric_stages.jl @@ -0,0 +1,3 @@ +include("fixtures/trigonometric_stage_contracts.jl") +trigonometric_stage_contracts(Array, Float32) +trigonometric_stage_contracts(Array, Float64) From 0462339dfa868b26a38095739f9efb9f34590176 Mon Sep 17 00:00:00 2001 From: PraneethMerugu Date: Wed, 9 Sep 2026 00:24:11 -0400 Subject: [PATCH 3/3] Resolve native math method ownership through public reflection --- CONTRIBUTING.md | 5 ++-- .../stage_program_kernelabstractions.jl | 25 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8badc8..f64e02b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,8 +107,9 @@ disabled bounds checks must not substitute for correct padded-lane behavior. Closed callable admission is owned by `src/execution/stage_program_kernelabstractions.jl`. Its narrowly enumerated -pure unary floating-point calls require a method owned by `Base.Math`, a -concrete `AbstractFloat` argument and the same return type. Extensions undergo +pure unary floating-point calls use public method reflection to require the same +Base-owned native math module as the Float64 method, a concrete `AbstractFloat` +argument and the same return type. Extensions undergo ordinary recursive effect analysis; this does not admit arbitrary foreign calls. `test/fixtures/trigonometric_stage_contracts.jl` exercises real sine/cosine publication through ordinary CPU and Metal stages, while diff --git a/src/execution/stage_program_kernelabstractions.jl b/src/execution/stage_program_kernelabstractions.jl index cd31b95..fd9f274 100644 --- a/src/execution/stage_program_kernelabstractions.jl +++ b/src/execution/stage_program_kernelabstractions.jl @@ -379,16 +379,27 @@ function _pointwise_residual_invoke_safe( return false end selected === method_instance.def || return false - if selected.module === Base.Math && any( + if any( candidate -> candidate === binding, _POINTWISE_PURE_UNARY_FLOAT_INVOKES ) - length(signature.parameters) == 1 || return false - argument_type = only(signature.parameters) - argument_type isa DataType && - argument_type <: AbstractFloat && - isconcretetype(argument_type) || return false - return code_instance.rettype === argument_type + # Derive the native math owner through public method reflection. An + # extension replacing the Float64 reference must not grant its module + # the intrinsic shortcut for other floating-point types. + native_owner = try + parentmodule(binding, Tuple{Float64}) + catch + nothing + end + if native_owner isa Module && parentmodule(native_owner) === Base && + selected.module === native_owner + length(signature.parameters) == 1 || return false + argument_type = only(signature.parameters) + argument_type isa DataType && + argument_type <: AbstractFloat && + isconcretetype(argument_type) || return false + return code_instance.rettype === argument_type + end end lowered = try code_lowered(binding, signature)