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

Zero arg syntax #7529

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft

Zero arg syntax #7529

wants to merge 8 commits into from

Conversation

smores56
Copy link
Collaborator

An in-progress change for my work to support zero-argument functions in userspace for Roc.

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

Can you post the compiler errors and/or runtime errors you are getting?

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

I'd probably suggest adding some infer_eq tests to solve to see what the issue is

@smores56
Copy link
Collaborator Author

I added two very basic solve tests to ensure that we give the correct return type for a generic zero-arg function. It worked with Dict and Num, so it's not looking like a type calculation problem, but a mono problem

@smores56
Copy link
Collaborator Author

The simplest test:

  The rockin' roc repl
────────────────────────

Enter an expression, or :help, or :q to quit.

» x = || 123
An internal compiler expectation was broken.
This is definitely a compiler bug.
Please file an issue here: <https://github.com/roc-lang/roc/issues/new/choose>
failed to find fn symbol for "#UserApp_repl_output_14253751639037753556"
Location: crates/compiler/gen_dev/src/object_builder.rs:1074:21

@smores56
Copy link
Collaborator Author

The above seems closer to the real problem, but there are plenty of alias analysis problems as well. Here's one from a roc_cli test:

smores@smortress:~/dev/roc$ cargo test -p roc_cli thunk
    Finished test [unoptimized + debuginfo] target(s) in 0.15s
     Running unittests src/lib.rs (target/debug/deps/roc_cli-5e550f57be510f3b)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s

     Running tests/cli_tests.rs (target/debug/deps/cli_tests-6b6f2e963e72105e)

running 1 test
test cli_tests::test_platform_simple_zig::run_multi_dep_thunk ... FAILED

failures:

---- cli_tests::test_platform_simple_zig::run_multi_dep_thunk stdout ----
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/test-projects/platform_requires_pkg/platform/glue
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/test-projects/algorithms/fibonacci-platform/glue
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/test-projects/algorithms/quicksort-platform/glue
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/benchmarks/platform/glue
Copied zig glue source files into /home/smores/dev/roc/crates/valgrind_tests/zig-platform/glue
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/test-projects/test-platform-effects-zig/glue
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/test-projects/test-platform-simple-zig/glue
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/test-projects/multiple_exposed/platform/glue
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/test-projects/tui/platform/glue
Copied zig glue source files into /home/smores/dev/roc/crates/cli/tests/platform-switching/zig-platform/glue
DONE
thread 'cli_tests::test_platform_simple_zig::run_multi_dep_thunk' panicked at crates/cli_test_utils/src/command.rs:82:9:
Command failed

Command: /home/smores/dev/roc/target/debug/roc build /home/smores/dev/roc/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/main.roc

Exit Code: exit status: 101

Stdout:


Stderr:
thread 'main' panicked at crates/compiler/gen_llvm/src/llvm/build.rs:5582:19:
Error in alias analysis: error in module ModName("UserApp"), function definition FuncName("\x10\x00\x00\x00\x00\x00\x00\x00l\x08\x12\xd4g1\x9bl"): expected type '(heap_cell,)', found type '()'
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    cli_tests::test_platform_simple_zig::run_multi_dep_thunk

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 60 filtered out; finished in 12.07s

error: test failed, to rerun pass `-p roc_cli --test cli_tests`

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

The simplest test:

  The rockin' roc repl
────────────────────────

Enter an expression, or :help, or :q to quit.

» x = || 123
An internal compiler expectation was broken.
This is definitely a compiler bug.
Please file an issue here: <https://github.com/roc-lang/roc/issues/new/choose>
failed to find fn symbol for "#UserApp_repl_output_14253751639037753556"
Location: crates/compiler/gen_dev/src/object_builder.rs:1074:21

Did you try running this with RUST_BACKTRACE=1?

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

The error is definitely coming from Morphic

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

The relevant part of the program that fails:

    fn "\x10\x00\x00\x00\x00\x00\x00\x00l\x08\x12\xd4g1\x9bl" (val_0: type_0) -> type_2 {
      let val_1 = make_tuple ();
      val_1
    } where {
      type type_0 = ();
      type type_1 = heap_cell;
      type type_2 = (type_1);
    }

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

It seems this is interesting:

Let(
        `#UserApp.var`,
        Struct(
            [],
        ),
        InLayout(
            23,
        ),
        Ret(
            `#UserApp.var`,
        ),
    ),
    ```

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

There would be a call there, but there isn't....

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

Here's the value def coming into can:

About to canonicalize_pending_value_def in module #UserApp
TypedBody(
    @95-99 Identifier {
        ident: "main",
    },
    @95-99 Identifier(
        `#UserApp.main`,
    ),
    @91-94 Apply(
        "",
        "Str",
        [],
    ),
    @107-134 Defs(
        Defs {
            tags: [
                EitherIndex(2147483648),
            ],
            regions: [
                @107-126,
            ],
            space_before: [
                Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
            ],
            space_after: [
                Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
            ],
            spaces: [],
            type_defs: [],
            value_defs: [
                Body(
                    @107-110 Identifier {
                        ident: "var",
                    },
                    @113-126 PncApply(
                        @113-124 Var {
                            module_name: "Dep2",
                            ident: "value2",
                        },
                        [],
                    ),
                ),
            ],
        },
        @131-134 Var {
            module_name: "",
            ident: "var",
        },
    ),
)
About to canonicalize_pending_value_def in module #UserApp
Body(
    @107-110 Identifier(
        `#UserApp.var`,
    ),
    @113-126 PncApply(
        @113-124 Var {
            module_name: "Dep2",
            ident: "value2",
        },
        [],
    ),
)

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

After canonicalizing the pending_value_def:

temp_def expr: @113-126 Call(
    (
        70,
        @113-124 Var(
            `Dep2.value2`,
            69,
        ),
        71,
        72,
        73,
    ),
    [],
    Space,
)

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

I think that creates/compiler/mono/src/ir.rs:8179 is an interesting thing to checkout. We apparently create a closure for imported functions (that have no fields because there is no capture needed). And then we don't do normal things with that closure afterwards (like call the function with it)

@gamebox
Copy link
Collaborator

gamebox commented Jan 20, 2025

But I have no idea. Somewhere this gets changed and I can't find it

@gamebox
Copy link
Collaborator

gamebox commented Jan 21, 2025

So I've tracked this down to what should be obvious, the layout representation for the lambda set is Struct, with zero fields. But I need to see what's different about have a single arg to figure out what we aren't doing.

@gamebox
Copy link
Collaborator

gamebox commented Jan 21, 2025

After edited your example so that the function called from main has a single arg and comparing the output, I see this.

mono/src/ir.rs (call_by_name_help):

        } else if field_symbols.is_empty() { // <- Zero args hits this, it thinks we are just assigning a func/closure to a var
            println!("field_symbols is empty");
            // this is a case like `Str.concat`, an imported standard function, applied to zero arguments

            // imported symbols cannot capture anything
            let captured = &[];
            debug_assert!(proc_name.no_captures());

            construct_closure_data(
                env,
                procs,
                layout_cache,
                lambda_set,
                proc_name,
                captured,
                assigned,
                hole,
            )
        } else {
            println!("None of the above"); // <- A single arg hits this.  it recognizes that this is an actual call.
            debug_assert_eq!(
                argument_layouts.len(),
                field_symbols.len(),
                "see call_by_name for background (scroll down a bit), function is {proc_name:?}",
            );

            let field_symbols = field_symbols.into_bump_slice();

            let call = self::Call {
                call_type: CallType::ByName {
                    name: proc_name,
                    ret_layout,
                    arg_layouts: argument_layouts,
                    specialization_id: env.next_call_specialization_id(),
                },
                arguments: field_symbols,
            };

            let result = build_call(env, call, assigned, ret_layout, hole);

            let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
            assign_to_symbols(env, procs, layout_cache, iter, result)
        }

@gamebox
Copy link
Collaborator

gamebox commented Jan 21, 2025

The fix:

        } else if field_symbols.is_empty() && !argument_layouts.is_empty() { // <- Check that there are also no argument layouts

Now, I'm not certain this is IT. But it does solve this test case.

Screenshot 2025-01-21 at 8 36 07 AM

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.

2 participants