Skip to content

Teach InteractiveUtils how to read ncat AST - #45399

Open
BioTurboNick wants to merge 30 commits into
JuliaLang:masterfrom
BioTurboNick:hvncat-interactiveutils
Open

BioTurboNick wants to merge 30 commits into
JuliaLang:masterfrom
BioTurboNick:hvncat-interactiveutils

Conversation

@BioTurboNick

Copy link
Copy Markdown
Contributor

Will resolve #43294

@BioTurboNick

BioTurboNick commented May 21, 2022 •

Copy link
Copy Markdown
Contributor Author

Well, that was a lot of work for an underwhelming result 😆

They all have the same entry point, so they all look like this:

julia> @code_lowered [3 4 ;;; 5 4]
CodeInfo(
1 ─ %1 = Core.tuple(dimsshape, row_first)
│   %2 = Core._apply_iterate(Base.iterate, Base._hvncat, %1, xs)
└──      return %2
)

The LLVM and assembly are more interesting

julia> @code_llvm [3 4 ;;; 5 4]
;  @ abstractarray.jl:2129 within `hvncat`
; Function Attrs: uwtable
define nonnull {}* @julia_hvncat_827([3 x i64]* nocapture nonnull readonly align 8 dereferenceable(24) %0, i8 zeroext %1, i64 signext %2, i64 signext %3, i64 signext %4, i64 signext %5) #0 {
top:
  %6 = alloca [7 x {}*], align 8
  %gcframe10 = alloca [7 x {}*], align 16
  %gcframe10.sub = getelementptr inbounds [7 x {}*], [7 x {}*]* %gcframe10, i64 0, i64 0
  %.sub = getelementptr inbounds [7 x {}*], [7 x {}*]* %6, i64 0, i64 0
  %7 = bitcast [7 x {}*]* %gcframe10 to i8*
  call void @llvm.memset.p0i8.i32(i8* nonnull align 16 dereferenceable(56) %7, i8 0, i32 56, i1 false)
  %8 = call {}*** inttoptr (i64 43440368 to {}*** ()*)() #4
; ┌ @ abstractarray.jl:2134 within `_hvncat`
   %9 = bitcast [7 x {}*]* %gcframe10 to i64*
   store i64 20, i64* %9, align 16
   %10 = getelementptr inbounds [7 x {}*], [7 x {}*]* %gcframe10, i64 0, i64 1
   %11 = bitcast {}** %10 to {}***
   %12 = load {}**, {}*** %8, align 8
   store {}** %12, {}*** %11, align 8
   %13 = bitcast {}*** %8 to {}***
   store {}** %gcframe10.sub, {}*** %13, align 8
   %ptls_field6 = getelementptr inbounds {}**, {}*** %8, i64 2305843009213693954
   %14 = bitcast {}*** %ptls_field6 to i8**
   %ptls_load78 = load i8*, i8** %14, align 8
   %15 = call noalias nonnull {}* @jl_gc_pool_alloc(i8* %ptls_load78, i32 1416, i32 32) #5
   %16 = bitcast {}* %15 to i64*
   %17 = getelementptr inbounds i64, i64* %16, i64 -1
   store atomic i64 289240400, i64* %17 unordered, align 8
   %18 = bitcast {}* %15 to i8*
   %19 = bitcast [3 x i64]* %0 to i8*
   call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 dereferenceable(24) %18, i8* nonnull align 8 dereferenceable(24) %19, i64 24, i1 false)
   %20 = and i8 %1, 1
   %.not = icmp eq i8 %20, 0
   %21 = select i1 %.not, {}* inttoptr (i64 286603920 to {}*), {}* inttoptr (i64 286531536 to {}*)
   %22 = getelementptr inbounds [7 x {}*], [7 x {}*]* %gcframe10, i64 0, i64 6
   store {}* %15, {}** %22, align 16
   %23 = call nonnull {}* @jl_box_int64(i64 signext %2)
   %24 = getelementptr inbounds [7 x {}*], [7 x {}*]* %gcframe10, i64 0, i64 5
   store {}* %23, {}** %24, align 8
   %25 = call nonnull {}* @jl_box_int64(i64 signext %3)
   %26 = getelementptr inbounds [7 x {}*], [7 x {}*]* %gcframe10, i64 0, i64 4
   store {}* %25, {}** %26, align 16
   %27 = call nonnull {}* @jl_box_int64(i64 signext %4)
   %28 = getelementptr inbounds [7 x {}*], [7 x {}*]* %gcframe10, i64 0, i64 3
   store {}* %27, {}** %28, align 8
   %29 = call nonnull {}* @jl_box_int64(i64 signext %5)
   %30 = getelementptr inbounds [7 x {}*], [7 x {}*]* %gcframe10, i64 0, i64 2
   store {}* %29, {}** %30, align 16
   store {}* inttoptr (i64 284363968 to {}*), {}** %.sub, align 8
   %31 = getelementptr inbounds [7 x {}*], [7 x {}*]* %6, i64 0, i64 1
   store {}* %15, {}** %31, align 8
   %32 = getelementptr inbounds [7 x {}*], [7 x {}*]* %6, i64 0, i64 2
   store {}* %21, {}** %32, align 8
   %33 = getelementptr inbounds [7 x {}*], [7 x {}*]* %6, i64 0, i64 3
   store {}* %23, {}** %33, align 8
   %34 = getelementptr inbounds [7 x {}*], [7 x {}*]* %6, i64 0, i64 4
   store {}* %25, {}** %34, align 8
   %35 = getelementptr inbounds [7 x {}*], [7 x {}*]* %6, i64 0, i64 5
   store {}* %27, {}** %35, align 8
   %36 = getelementptr inbounds [7 x {}*], [7 x {}*]* %6, i64 0, i64 6
   store {}* %29, {}** %36, align 8
   %37 = call nonnull {}* @jl_invoke({}* inttoptr (i64 352304192 to {}*), {}** nonnull %.sub, i32 7, {}* inttoptr (i64 206632560 to {}*))
   %38 = load {}*, {}** %10, align 8
   %39 = bitcast {}*** %8 to {}**
   store {}* %38, {}** %39, align 8
; └
  ret {}* %37
}

julia> @code_native [3 4 ;;; 5 4]
        .text
; ┌ @ abstractarray.jl:2129 within `hvncat`
        pushq   %rbp
        movq    %rsp, %rbp
        pushq   %r15
        pushq   %r14
        pushq   %r13
        pushq   %r12
        pushq   %rsi
        pushq   %rdi
        pushq   %rbx
        andq    $-32, %rsp
        subq    $192, %rsp
        movq    %r9, 56(%rsp)
        movq    %r8, %rsi
        movl    %edx, %ebx
        movq    %rcx, %r14
        vxorps  %xmm0, %xmm0, %xmm0
        vmovups %ymm0, 88(%rsp)
        vmovaps %ymm0, 64(%rsp)
        movl    $jl_get_pgcstack, %eax
        vzeroupper
        callq   *%rax
        movq    %rax, %r15
; │┌ @ abstractarray.jl:2134 within `_hvncat`
        movq    $20, 64(%rsp)
        movq    (%r15), %rax
        movq    %rax, 72(%rsp)
        leaq    64(%rsp), %rax
        movq    %rax, (%r15)
        movq    16(%r15), %rcx
        movabsq $jl_gc_pool_alloc, %rax
        movl    $1416, %edx                     # imm = 0x588
        movl    $32, %r8d
        callq   *%rax
        movq    %rax, %rdi
        movq    $289240400, -8(%rdi)            # imm = 0x113D7550
        movq    16(%r14), %rax
        movq    %rax, 16(%rdi)
        vmovups (%r14), %xmm0
        vmovups %xmm0, (%rdi)
        testb   $1, %bl
        movl    $286603920, %eax                # imm = 0x11153A90
        movl    $286531536, %r12d               # imm = 0x11141FD0
        cmoveq  %rax, %r12
        movq    %rdi, 112(%rsp)
        movabsq $jl_box_int64, %r13
        movq    %rsi, %rcx
        callq   *%r13
        movq    %rax, %r14
        movq    %r14, 104(%rsp)
        movq    56(%rsp), %rcx
        callq   *%r13
        movq    %rax, %rsi
        movq    %rsi, 96(%rsp)
        movq    48(%rbp), %rcx
        callq   *%r13
        movq    %rax, %rbx
        movq    %rbx, 88(%rsp)
        movq    56(%rbp), %rcx
        callq   *%r13
        movq    %rax, 80(%rsp)
        movq    $284363968, 128(%rsp)           # imm = 0x10F30CC0
        movq    %rdi, 136(%rsp)
        movq    %r12, 144(%rsp)
        movq    %r14, 152(%rsp)
        movq    %rsi, 160(%rsp)
        movq    %rbx, 168(%rsp)
        movq    %rax, 176(%rsp)
        movabsq $jl_invoke, %rax
        leaq    128(%rsp), %rdx
        movl    $352304192, %ecx                # imm = 0x14FFBC40
        movl    $206632560, %r9d                # imm = 0xC50F670
        movl    $7, %r8d
        callq   *%rax
        movq    72(%rsp), %rcx
        movq    %rcx, (%r15)
; │└
        leaq    -56(%rbp), %rsp
        popq    %rbx
        popq    %rdi
        popq    %rsi
        popq    %r12
        popq    %r13
        popq    %r14
        popq    %r15
        popq    %rbp
        retq
        nopl    (%rax,%rax)
; └

@BioTurboNick
BioTurboNick marked this pull request as ready for review May 22, 2022 01:29
@BioTurboNick BioTurboNick changed the title Draft: Teach InteractiveUtils how to read ncat AST Teach InteractiveUtils how to read ncat AST May 22, 2022
@BioTurboNick

Copy link
Copy Markdown
Contributor Author

Might be good to get this out of the way and into 1.11, if there are no objections. @KristofferC

@BioTurboNick

Copy link
Copy Markdown
Contributor Author

This should probably make it in at some point, right?

@PallHaraldsson

Copy link
Copy Markdown
Contributor

Yes, merge it? Or are test failures real (and making this overlooked, rerun them?)? It seems like a false alarm on apple but not sure about the other, then REPL related (only on 32-bit, strange).

@BioTurboNick

Copy link
Copy Markdown
Contributor Author

They both seem to be timeouts? The REPL error in the Linux 32-bit runner also occurs in 32-bit Windows CI but doesn't make it fail, so I think it's unrelated. I'll see if it's a known issue.

@BioTurboNick

BioTurboNick commented Oct 15, 2025 •

Copy link
Copy Markdown
Contributor Author

@adienes

adienes commented Oct 15, 2025

Copy link
Copy Markdown
Member

I get this:

julia> @code_lowered [3 4 ;;; 5 4]
ERROR: LoadError: UndefVarError: `typesof` not defined in `InteractiveUtils`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] gen_call_with_extracted_types(__module__::Module, fcn::Symbol, ex0::Expr, kws::Vector{…}; is_source_reflection::Bool, supports_binding_reflection::Bool, use_signature_tuple::Bool)
   @ InteractiveUtils ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:593
 [2] gen_call_with_extracted_types
   @ ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:386 [inlined]
 [3] #gen_call_with_extracted_types_and_kwargs#75
   @ ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:645 [inlined]
 [4] kwcall(::@NamedTuple{…}, ::typeof(InteractiveUtils.gen_call_with_extracted_types_and_kwargs), __module__::Module, fcn::Symbol, ex0::Tuple{…})
   @ InteractiveUtils ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:631
 [5] var"@code_lowered"(__source__::LineNumberNode, __module__::Module, ex0::Vararg{Any})
   @ InteractiveUtils ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:673
 [6] fl_lower
   @ ./flfrontend.jl:24 [inlined]
 [7] fl_lower(ex::Expr, mod::Module, filename::Ptr{UInt8}, lineno::UInt64)
   @ Base ./flfrontend.jl:23
in expression starting at REPL[2]:1
Some type information was truncated. Use `show(err)` to see complete types.

would it be possible to add a test case or two? maybe with ;; in different positions & combinations, mixing in spaces / commas, etc.

Comment thread stdlib/InteractiveUtils/src/macros.jl Outdated
Comment thread stdlib/InteractiveUtils/src/macros.jl Outdated
@BioTurboNick

Copy link
Copy Markdown
Contributor Author

I get this:

julia> @code_lowered [3 4 ;;; 5 4]
ERROR: LoadError: UndefVarError: `typesof` not defined in `InteractiveUtils`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] gen_call_with_extracted_types(__module__::Module, fcn::Symbol, ex0::Expr, kws::Vector{…}; is_source_reflection::Bool, supports_binding_reflection::Bool, use_signature_tuple::Bool)
   @ InteractiveUtils ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:593
 [2] gen_call_with_extracted_types
   @ ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:386 [inlined]
 [3] #gen_call_with_extracted_types_and_kwargs#75
   @ ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:645 [inlined]
 [4] kwcall(::@NamedTuple{…}, ::typeof(InteractiveUtils.gen_call_with_extracted_types_and_kwargs), __module__::Module, fcn::Symbol, ex0::Tuple{…})
   @ InteractiveUtils ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:631
 [5] var"@code_lowered"(__source__::LineNumberNode, __module__::Module, ex0::Vararg{Any})
   @ InteractiveUtils ~/.julia/juliaup/julia-pr45399/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:673
 [6] fl_lower
   @ ./flfrontend.jl:24 [inlined]
 [7] fl_lower(ex::Expr, mod::Module, filename::Ptr{UInt8}, lineno::UInt64)
   @ Base ./flfrontend.jl:23
in expression starting at REPL[2]:1
Some type information was truncated. Use `show(err)` to see complete types.

would it be possible to add a test case or two? maybe with ;; in different positions & combinations, mixing in spaces / commas, etc.

Seems that this commit in master, but not 1.12, broke it: c3e7b1b

I'll have to adjust.

@BioTurboNick

Copy link
Copy Markdown
Contributor Author

I don't understand why test x86_64-linux-gnuassertrr keeps failing.

@PallHaraldsson

Copy link
Copy Markdown
Contributor

I'm guessing most likely a false alarm (so just finally merge this?); maybe caused by this:

errorshow (10) | started at 2025-11-20T16:12:39.526 on pid 722
..
From worker 10: Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
From worker 10: Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.

..
[more effects]
..
boundscheck (2) | failed at 2025-11-20T16:17:02.168
ProcessExitedException(2)

I'm pretty sure this is also a false alarm:

No test results found from test_x86_64-linux-gnuassertrr
This could mean:

  • The test step hasn't completed successfully yet
  • The test step failed and produced no results
  • This upload job was triggered manually before test completion
    You can re-run this job manually once test results are available.
    🚨 Error: The command exited with status 1

@BioTurboNick

BioTurboNick commented Nov 24, 2025 •

Copy link
Copy Markdown
Contributor Author

Thanks, guess I'll keep updating the branch until CI passes and it gets merged.

@BioTurboNick

Copy link
Copy Markdown
Contributor Author

Oh, I was going to add tests first.

@BioTurboNick

Copy link
Copy Markdown
Contributor Author

It's ready; Windows CI failure in Profile is surely unrelated.

@BioTurboNick
BioTurboNick requested a review from adienes February 3, 2026 03:41
@adienes

adienes commented Feb 6, 2026

Copy link
Copy Markdown
Member

it doesn't seem that extract_elements here handles the leaves (aka actual items in the concatenation expression) and will always return [] or nothing, resulting in issues like these examples:

julia> @macroexpand @which [1;;;]
:(InteractiveUtils.which((InteractiveUtils.make_tuple_type)(InteractiveUtils.Any[Core.Typeof(hvncat), Core.Typeof(3), Core.Typeof(nothing)]); ))

julia> @macroexpand @which [rand(2);;;]
:(InteractiveUtils.which((InteractiveUtils.make_tuple_type)(InteractiveUtils.Any[Core.Typeof(hvncat), Core.Typeof(3), Core.Typeof(Any[])]); ))

so note how in the first, we passed a literal 1 but the macro sees Core.Typeof(nothing) and in the second we passed rand(2) (could be any function call) and the macro sees Core.Typeof(Any[])

two other general notes:

  • I think trying to recreate the lowering logic in a macro is going to be hard to get right, but more importantly it's going to be extremely hard to maintain, since if that lowering ever changes this macro would have to change in tandem. maybe instead, this PR could inspect the results of Meta.@lower [1;;;] directly? I know that's a bit atypical but it seems more maintainable
  • ideally, the tests would be more picky than just checking that the head is :hvncat and actually test that we hit the right signature

@BioTurboNick

Copy link
Copy Markdown
Contributor Author

Thank you for finding those issues. I take your point about maintainability, but I got it working. Fixed extract_element code, then got frustrated with get_shape and tried out Claude Code on it; with proper description of the problem and examples, then having it make it more concise, it actually worked. Then I added test cases by hand. I don't love how I'm diving deep into an Expr object to do it though.

@adienes

adienes commented Feb 7, 2026

Copy link
Copy Markdown
Member

the tests are better, thanks. but I think the else return [] in extract_elements is still not correct. this will not observe the result of any expression in a concatenation, e.g. @which [string(1) string(2)] will just send you to the generic fallback even if some String specialization existed

@BioTurboNick

Copy link
Copy Markdown
Contributor Author

Ah, got it. Thanks!

image

Comment thread stdlib/InteractiveUtils/src/macros.jl Outdated
Comment thread stdlib/InteractiveUtils/src/macros.jl Outdated
@BioTurboNick

This comment was marked as resolved.

Comment thread test/JuliaLowering_stdlibs.jl Outdated
mlechu added a commit that referenced this pull request Feb 25, 2026
Became an issue in #45399 (cc
@BioTurboNick).

- Port #58940 and steal most
tests (we should figure out how to just run the existing tests soon)
- Add a version of `@__FUNCTION__` that preserves provenance
- Catch misuse in the validator
- Fix a bug where generated functions weren't being counted as functions
mkitti pushed a commit to mkitti/julia that referenced this pull request May 22, 2026
…#61150)

Became an issue in JuliaLang#45399 (cc
@BioTurboNick).

- Port JuliaLang#58940 and steal most
tests (we should figure out how to just run the existing tests soon)
- Add a version of `@__FUNCTION__` that preserves provenance
- Catch misuse in the validator
- Fix a bug where generated functions weren't being counted as functions
@BioTurboNick

Copy link
Copy Markdown
Contributor Author

Should we get this in for 1.14?

@adienes

adienes commented Sep 24, 2026

Copy link
Copy Markdown
Member

I'm out of tokens but if ^(astra|fable)$ don't find anything else IIRC it was looking good

@BioTurboNick

Copy link
Copy Markdown
Contributor Author

Good call - Opus 5.5 (Medium) found a couple of issues and a bug in the Scheme lowering of the original implementation (that was already fixed in JuliaLowering). So for consistency, I'm also matching JuliaLowering and also fixing Scheme here.

Then after committing in a fresh session I repeated the request to Fable 5.1, which found a couple of minor nits.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InteractiveUtils needs to be made aware of hvncat syntax

5 participants