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

ability to mark functions as wanting to be comptime if possible #425

Closed
andrewrk opened this issue Aug 19, 2017 · 20 comments
Closed

ability to mark functions as wanting to be comptime if possible #425

andrewrk opened this issue Aug 19, 2017 · 20 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

Use case: if the parameter to assert is comptime known, we for sure want to execute the assert function call at compile-time. It would catch a lot of bugs at compile-time instead of generating, essentially assert(false) in some places.

So here's a proposal to have a way to mark a function as "eager to be comptime", which means that if all parameters are comptime known, the compiler executes the function and uses the result instead of emitting a runtime function call.

We can't have all functions do this, because functions can contain runtime side effects even if all params are comptime known, and we do want to have compile errors for hitting runtime side effects at compile time for these kind of functions. Case in point, we want the if (!ok) unreachable to be a compile-error in the assert function if ok is false. So this is why the user has to mark a function as "eager to be run at compile time".

Here's one syntax proposal:

comptime fn assert(ok: bool) {
    if (!ok) unreachable;
}

The thing I don't like about this is it looks like assert always has to be run at compile-time, but it doesn't. It also looks like you have to mark a function comptime before you can use it at compile-time, and that's also not true.

@andrewrk andrewrk added the enhancement Solving this issue will likely involve adding new logic or components to the codebase. label Aug 19, 2017
@andrewrk andrewrk added this to the 0.2.0 milestone Aug 19, 2017
@raulgrell
Copy link
Contributor

raulgrell commented Aug 20, 2017

I was going to propose something along these lines, where you could mark a function as either optionally comptime or must be comptime. I also didn't know which syntax would be best for these scenarios. Something like:

Eager comptime:

comptime fn assert(ok: bool) {
    if (!ok) unreachable;
}

and enforced comptime

fn comptime_assert(ok: bool) -> comptime void {
    if (!ok) unreachable;
}

@thejoshwolfe
Copy link
Sponsor Contributor

For assert, what about just marking it inline? Then the caller will get if (comptime something) unreachable which becomes if (false) unreachable which becomes unreachable, which is a compile error.

@andrewrk
Copy link
Member Author

export fn foo() {
    unreachable;
}

compiles fine currently. Should this be a compile error? "control flow guaranteed to hit unreachable code"

if so then I really like the idea that marking assert inline would have this behavior. That would also require implementing inlining in Zig (frontend) rather than using the LLVM always_inline attribute (backend).

which maybe we want, because sometimes LLVM doesn't inline even when we put always_inline attribute on. I made this an error, but it's kind of like a hopeless, welp, it didn't work, nothing zig can do about it, kind of error.

@thejoshwolfe
Copy link
Sponsor Contributor

"control flow guaranteed to hit unreachable code" sounds like the halting problem. What about a function that unconditionally hits unreachable but that's called conditionally?

I think inline is a red herring. Let's go back to the original proposal.

Can you give an example of code that shows this issue? i can imagine cases where we do want to generate assert(false), for example:

switch (event.type) {
    EventType.PosixSignal => {
        assert(!is_windows);
        foo(event);
    }
}

andrewrk referenced this issue in tiehuis/ansiz Oct 11, 2017
@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Jan 18, 2018
@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Feb 28, 2018
@scallyw4g
Copy link

I sat in on a live stream @andrewrk did last week and had some thoughts about this.

IIRC the state of comptime at this time (or at least at the time of the stream) is that a block or function at the call site can be marked as comptime. This is good and I like that it's 100% explicit, however it may become overly verbose in the case of something like assert.

Continuing down this line of thinking, we'd like to be able to mark functions as being run at compile time - a reasonable ask.

I believe that there's an argument to be made for having a pair of keywords for this. One keyword says "Mr zig compiler, please only ever run this function at compile time", while the other keyword says "Run this at compile time if possible, but also generate a runtime counterpart".

With the assert example in mind we would end up with two functions, assert and comptime_assert, which work in their respective "time scopes?" - assert is a regular function and comptime_assert is only ever evaluated at compile time.

The reason I propose this is a bug I thought about that it guards against. Imagine we're doing compile time string hashing. If our string hash function accidentally gets passed a runtime string in a place we're expecting it to be passed a const string we're going to have a performance bug. Similarly, if a single assert function worked both at runtime and compile time, there's a chance we would accidentally pass it a runtime value, compile fine, then be oblivious to the fact we may have a bug in that path.

I particularly think the assert case is a compelling reason to have at least a way of saying "only ever run this at compile time", but it would be convenient to have both to sometimes avoid code duplication.

Not sure if this is a useful idea, or already solved in some other way, but I thought it would be worthwhile sharing.

Cheers,
Jesse

@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. and removed enhancement Solving this issue will likely involve adding new logic or components to the codebase. labels Nov 19, 2018
@tgschultz
Copy link
Contributor

Marking comptime functions in this fashion seems to me to be similar to marking them inline. We don't have an "inline if you can but otherwise just roll with it" keyword either. I'm not convinced of the need for that, since I imagine the optimizer will probably inline it wherever it makes sense to do so for performance if it can anyway. The same is probably true of comptime.

So if we were to match the status quo for inline, then marking a function comptime asserts that it will be run at compile time. If you want it to be either, then write it so it can be run at compile time but don't mark it, and leave it up to the caller to explicitly say they want comptime.

Alternatively, instead of adding keywords I think it would make sense to use keyword parameters to state intent. comptime<Always>, comptime<Preferred> or even comptime<Never>. Ditto for inline.

@andrewrk andrewrk modified the milestones: 0.4.0, 0.5.0 Nov 21, 2018
@andrewrk andrewrk modified the milestones: 0.5.0, 0.6.0 Jul 5, 2019
andrewrk added a commit that referenced this issue Oct 24, 2019
It had the downside of running all the comptime blocks and resolving
all the usingnamespaces of each system, when just trying to discover if
the current system is a particular one.

For Darwin, where it's nice to use `std.Target.current.isDarwin()`, this
demonstrates the utility that #425 would provide.
@JesseRMeyer
Copy link

If I have to guess whether any given assert(cond); in my code could be executed at either comp or run time, I'll be more likely to get confused about what invariants are present and make mistakes difficult to diagnose.

Better to make it explicit. comptime assert(cond); is flexible and communicates intent exactly. But there is value is specifying that a function should always be comptime executed by way of the qualifier.

andrewrk added a commit that referenced this issue Nov 29, 2019
All four of these can be represented in fewer characters with
expressions, which will be guaranteed to happen at compile-time, and
have the same or better precision.

The other math constants here which depend on function calls could be
similarly removed if and when #425 is solved. However I left them for
now since Zig does not eagerly evaluate functions with comptime parameters.
@pgruenbacher
Copy link

I'm pretty sure what you're wanting is similar to this talk https://www.youtube.com/watch?v=bIc5ZxFL198
unfortunately in the talk he doesn't have an idea either of what a maybe constexpr should beeither.

@ikskuh
Copy link
Contributor

ikskuh commented Oct 13, 2020

Another idea would be to add a comptime callconv:

fn foo() callconv(.Comptime) void {

}

which is imho the most "natural" extension. A .Inline function isn't called either, but influenced codegen to insert that function code at the callsite, so these are also more like macros and less like functions. The same is for .Comptime functions: They are not "called" at runtime, but are just a really fancy way to write literal values.

@pixelherodev
Copy link
Contributor

That's definitely an improvement over my suggestion. I second that :)

@Rocknest
Copy link
Contributor

@MasterQ32 but how exactly this calling convention works? Are any runtime side effects allowed? (Does this work like comptime in the global scope?)

The original proposal is for 'functions eager to be comptime', its a 'transitive comptime' property if all arguments are comptime => do a comptime function call.

@SpexGuy
Copy link
Contributor

SpexGuy commented Jan 5, 2021

More insight on this issue being closed:
Inline functions will preserve comptime-known-ness of the parameters and return value. This satisfies the comptime-only use case - mark all parameters as comptime and the function as inline, and use a comptime block within the function, and the result will be comptime known. Since it's inline, no runtime version of the function will be generated.

After discussing this for a while at a design meeting, we were unable to come up with a satisfactory solution for the "comptime if possible, otherwise runtime" case. Closing this issue does not preclude a future proposal for such a feature, but for now we are acknowledging this as a limitation of the language. We are not convinced that this use case requires a solution. If you have Real Actual Use Cases for this feature, please open a new issue.

@marler8997
Copy link
Contributor

Should this issue be re-opened until the accepted inline=comptime behavior is implemented? Or should we open another issue for that?

@roryokane
Copy link
Contributor

roryokane commented Sep 6, 2021

Four files in the standard library have comments linking to this issue, despite it being now closed:

Mentions of https://github.com/ziglang/zig/issues/425 in the code

zig/lib/std/c.zig

Lines 16 to 17 in 34671b1

/// The return type is `type` to force comptime function call execution.
/// TODO: https://github.com/ziglang/zig/issues/425

/// TODO: https://github.com/ziglang/zig/issues/425

/// TODO Nearly all the functions in this namespace would be
/// better off if https://github.com/ziglang/zig/issues/425
/// was solved.

/// TODO: https://github.com/ziglang/zig/issues/425

If a successor issue has been created, I presume those comments should be updated to point to that issue. Alternatively, if you think this use-case will never be addressed, those comments should be removed.

@jibal
Copy link

jibal commented Jan 29, 2022

"control flow guaranteed to hit unreachable code" sounds like the halting problem.

The halting problem is to create a single algorithm that can determine whether any given TM halts. Contrary to common belief, the unsolvability of the halting problem has no practical implications. Even if it were solvable, there would be TMs for which the analysis would take a very long time, so in any case there has to be a time cutoff when you give up and say that you can't determine whether the code halts. But there are vastly many pieces of code for which it is easy to determine whether they terminate. It's particularly easy for empty code, which is what we have here between fn foo() { and unreachable ... there's no need to solve the halting problem in order to determine that, if foo is called, the unreachable assertion will be violated. And that's what it is, an assertion ... so the message should be something more like "control flow guaranteed to violate 'unreachable' assertion".

@jibal
Copy link

jibal commented Mar 26, 2022

I have dozens--heading toward hundreds--of functions that always operate at comptime and they all have to be explicitly qualified with comptime at the call site and every one is a potential bug ... so I have changed them to return a type (a struct with a const containing the actual value) or comptime_int rather than bool (mostly) ... why can't we simply declare a function to return comptime bool? For that matter, it should be possible to have const comptime_bool = comptime bool; and then you could do var foo: comptime_bool instead of comptime var foo: bool ... this is important in generic code where the type depends on the parameters; var foo: T is a compilation error if T is a comptime type. This is a major roadblock in writing a large generic library.

@ghost
Copy link

ghost commented Oct 1, 2022

This issue is closed, but in lib/std/target.zig there's a comment:

/// TODO Nearly all the functions in this namespace would be
/// better off if https://github.com/ziglang/zig/issues/425
/// was solved.

What's the status? Should the comment be removed?

@likern
Copy link

likern commented Feb 19, 2023

I came across this too.

@jibal
Copy link

jibal commented Feb 19, 2023

I gave up on my generic library and then on Zig itself because of its wrongheaded approach to comptime -- it doesn't allow marking a function as returning comptime except for magic types like comptime_int, it doesn't allow making comptime part of a type, it doesn't allow making code conditional on whether its being run at comptime ... I explained in my comment above and at #868 why this is a problem.

andrewrk pushed a commit that referenced this issue Jul 29, 2023
Removes a comment referencing #425 which has been closed
QusaiHroub pushed a commit to QusaiHroub/zig that referenced this issue Aug 3, 2023
Removes a comment referencing ziglang#425 which has been closed
wooster0 added a commit to wooster0/zig that referenced this issue Aug 26, 2023
GitHub issue ziglang#425 has been closed
and this cleans up remaining code that was written when that issue was
open.
wooster0 added a commit to wooster0/zig that referenced this issue Aug 26, 2023
GitHub issue ziglang#425 has been closed
and this cleans up remaining code that was written when that issue was
open.
wooster0 added a commit to wooster0/zig that referenced this issue Sep 18, 2023
wrongnull added a commit to wrongnull/zig that referenced this issue Sep 24, 2023
Since ziglang#425 is closed, changes in this pr are now valid
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests