Skip to content

Conversation

@vtjnash
Copy link
Member

@vtjnash vtjnash commented Aug 6, 2025

This commit implements a Task optimizations by adding a invoked field to the Task definition, which optionally causes it to do an invoke call internally instead of a regular call.

Key changes:

  • Add invoked field to Task structure, supporting Type, Method, MethodInstance, and CodeInstance, just like invoke
  • Implement _task builtin function to construct Task
  • Create PartialTask lattice element for precise task result and error type tracking
  • Unify several CallInfo types into IndirectCallInfo
  • Optimized calls to _task constructor to inject CodeInstance. Calling fully_covers isn't necessary, as the runtime will check that.

In the future we can consider making this field user-inaccessible (like scope) and then be able to optimize based on the type returned from fetch.

Also added documentation to base/docs/basedocs.jl on how to easily add more Builtin functions, following this as an example.

🤖 Generated with help from Claude Code

@vtjnash vtjnash requested a review from aviatesk August 6, 2025 15:31
- `modifyglobal!(mod, var, op, x, order)` where `op(getval(), x)` is called
- `memoryrefmodify!(memref, op, x, order, boundscheck)` where `op(getval(), x)` is called
- `Intrinsics.atomic_pointermodify(ptr, op, x, order)` where `op(getval(), x)` is called
- `Core._task(f, size)` where `f()` will be called when the task runs
Copy link
Member

Choose a reason for hiding this comment

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

Aren't task and finalizer different from the modify functions in that the modify functions eagerly call the argument at the call site, while task and finalizer are deferred? I thought that was important.

Copy link
Member Author

Choose a reason for hiding this comment

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

It isn't important. All that matters is that there is some separate broker function (e.g. like https://llvm.org/doxygen/classllvm_1_1AbstractCallSite.html)

error("cannot serialize a running Task")
end
if isdefined(t, :invoked)
error("cannot serialize a Task constructed with invoke info")
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure this is helpful, since if you hit this you can't really control it. We could just skip this field and hope it works out.

@JeffBezanson
Copy link
Member

Any benchmarks?

@aviatesk
Copy link
Member

aviatesk commented Aug 6, 2025

Any benchmarks?

I was unable to confirm optimization results even in the simplest case on my end unfortunately.
Perhaps task construction/scheduling is becoming the bottleneck?

julia> func2(i) = fetch(Threads.@spawn sin(i));

julia> using BenchmarkTools

julia> @benchmark func2(42)

master:

BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  709.000 ns …  34.583 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):      12.667 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):    12.857 μs ± 976.688 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

                                            ▁▃▅▅█▇▄▄▅▄▃▂▁▁      ▂
  ▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄████████████████▇▆▇ █
  709 ns        Histogram: log(frequency) by time       16.2 μs <

 Memory estimate: 352 bytes, allocs estimate: 6.

this PR:

BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  11.333 μs …  29.542 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     12.708 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   12.602 μs ± 587.109 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▄▆▃         ▁   ▁▄▅▆▅▄▃▄▇▇██▆▆▅▂▁▁▂ ▁▁▁   ▁                  ▃
  ███▇▇▆▆▇█▇▇▇█▇▇████████████████████████▇▇▇██▇▇▇█▆▅▆█▆▆▆▇▆▄▆▅ █
  11.3 μs       Histogram: log(frequency) by time      14.4 μs <

 Memory estimate: 352 bytes, allocs estimate: 6.

@vtjnash
Copy link
Member Author

vtjnash commented Aug 6, 2025

It was entirely process_events and scheduling issues for me, which are separately addressable. The main point is inference and trim, not performance at this time.

@vtjnash vtjnash force-pushed the jn/Task-fetch-inference branch from 6c17db1 to df3c99d Compare September 10, 2025 20:17
@vtjnash vtjnash requested a review from aviatesk September 10, 2025 20:17
@vtjnash vtjnash added the compiler:inference Type inference label Sep 10, 2025
This commit implements a Task optimizations by adding a `invoked` field
to the Task definition, which optionally causes it to do an `invoke`
call internally instead of a regular `call`.

Key changes:
  - Add `invoked` field to Task structure, supporting Type, Method, MethodInstance, and CodeInstance, just like invoke
  - Implement `_task` builtin function to construct Task
  - Create PartialTask lattice element for precise task result and error type tracking
  - Unify several CallInfo types into IndirectCallInfo
  - Optimized calls to `_task` constructor to inject CodeInstance.
    Calling `fully_covers` isn't necessary, as the runtime will check that.

In the future we can make this field user-inaccessible (like scope) and
then be able to optimize based on the type returned from `fetch`.

Also added documentation to base/docs/basedocs.jl on how to easily add
more Builtin functions, following this as an example.

🤖 Generated with help from [Claude Code](https://claude.ai/code)
Very similar to return_type. Because of the design of Task reusing the
`result` field to mean a lot of different things, it is hard to make
this inference safe and sound now. Try anyways to add a type-assert so
that `fetch` is inferred even if the design of Task make it unsafe.

# task_result_type effects modeling (should have !consistent effect)
let effects = Base.infer_effects(Core.task_result_type, (Task,))
@test !Compiler.is_consistent(effects) # !consistent bit should be set
Copy link
Member

Choose a reason for hiding this comment

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

Is this because of Task's mutability? I know this has been talked about a lot already, but can't we make Task immutable in its semantics?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it is because inference becomes dangerously unsound if you accidentally ever infer the result of inference to be consistent

@aviatesk aviatesk force-pushed the jn/Task-fetch-inference branch from df3c99d to c6a6187 Compare September 14, 2025 17:16
@aviatesk
Copy link
Member

Rebased, added some higher-level tests with Threads.@spawn, and simplified the inlining.jl implementation a bit.

@vtjnash vtjnash added the merge me PR is reviewed. Merge when all tests are passing label Sep 18, 2025
@aviatesk
Copy link
Member

Looks like now we are hitting this assertion:

Error in testset Serialization:
Error During Test at /cache/build/tester-amdci4-8/julialang/julia-master/julia-dfe6ed50b7/share/julia/test/testdefs.jl:25
  Got exception outside of a @test
  LoadError: cannot serialize a Task constructed with invoke info
  Stacktrace:
    [1] error(s::String)
      @ Base ./error.jl:44
    [2] serialize(s::Serializer{IOBuffer}, t::Task)
      @ Serialization /cache/build/tester-amdci4-8/julialang/julia-master/julia-dfe6ed50b7/share/julia/stdlib/v1.13/Serialization/src/Serialization.jl:502
    [3] serialize(s::IOBuffer, x::Task)
      @ Serialization /cache/build/tester-amdci4-8/julialang/julia-master/julia-dfe6ed50b7/share/julia/stdlib/v1.13/Serialization/src/Serialization.jl:818
    [4] (::Main.Test43Main_Serialization.var"#76#77")(s::IOBuffer)
      @ Main.Test43Main_Serialization /cache/build/tester-amdci4-8/julialang/julia-master/julia-dfe6ed50b7/share/julia/stdlib/v1.13/Serialization/test/runtests.jl:359
    [5] create_serialization_stream(f::Main.Test43Main_Serialization.var"#76#77")
      @ Main.Test43Main_Serialization /cache/build/tester-amdci4-8/julialang/julia-master/julia-dfe6ed50b7/share/julia/stdlib/v1.13/Serialization/test/runtests.jl:13
    [6] top-level scope
      @ /cache/build/tester-amdci4-8/julialang/julia-master/julia-dfe6ed50b7/share/julia/stdlib/v1.13/Serialization/test/runtests.jl:355
    [7] include(mod::Module, _path::String)
      @ Base ./Base.jl:308
    [8] macro expansion

@aviatesk aviatesk removed the merge me PR is reviewed. Merge when all tests are passing label Sep 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:inference Type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants