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

Feature request - Foreach over variable or constant lists #137

Closed
tom-leys opened this issue Sep 4, 2024 · 9 comments
Closed

Feature request - Foreach over variable or constant lists #137

tom-leys opened this issue Sep 4, 2024 · 9 comments

Comments

@tom-leys
Copy link

tom-leys commented Sep 4, 2024

Instead of this construct:

for n in 1 .. 3
    sort = case n
        when 1
            SORT_1
        when 2
            SORT_2
        when 3
            SORT_3
    end
    conv = case n
        when 1
            CONV_1
        when 2
            CONV_2
        when 3
            CONV_3
    end

    item_type = sort.config
    item_count = CONTAINER.sensor(item_type)
    under_limit = item_count < QTY_LIMIT
    conv.enabled = under_limit
end

It would be nice to write something like this

foreach sort, conv in [(SORT_1, CONV_1), (SORT_2, CONV_2), (SORT_3, CONV_3)]
    item_type = sort.config
    item_count = CONTAINER.sensor(item_type)
    under_limit = item_count < QTY_LIMIT
    conv.enabled = under_limit
end

This is like list iteration and tuple unpacking in Python (I haven't used ruby). Even nicer if there is a way to take output values from a loop and assign those to various values depending on index also.

I guess the current workaround is to make a list of function calls instead, which is probably the process I'll need to start using. Though that would require #138

@tom-leys
Copy link
Author

tom-leys commented Sep 4, 2024

For further motivation, see this example (from #136)

for reactor_idx in 1..5 
    emerg_shutdown = check_radar(TOWER)

    reactor = case reactor_idx
        when 1
            fuel = fuel_1
            reactor1
        when 2
            fuel = fuel_2
            reactor2
        when 3
            fuel = fuel_3
            reactor3
        when 4
            fuel = fuel_4
            reactor4
        when 5
            fuel = fuel_5
            reactor5
    end

    fuel = start_reactor(reactor, reactor_idx, emerg_shutdown, fuel)

    case reactor_idx
        when 1
            fuel_1 = fuel
            ULD_IN_1.config = fuel_1?FUEL:FUEL_OFF
            ULD_OUT_1.config = !fuel_1?FUEL:FUEL_OFF
        when 2
            fuel_2 = fuel
            ULD_IN_2.config = fuel_2?FUEL:FUEL_OFF
            ULD_OUT_2.config = !fuel_2?FUEL:FUEL_OFF
        else
            case reactor_idx
                when 3
                    fuel_3 = fuel
                    reactor3
                when 4
                    fuel_4 = fuel
                    reactor4
                when 5
                    fuel_5 = fuel
                    reactor5
            end
            fuel_many = fuel_3 or fuel_4 or fuel_5
            ULD_IN_MANY.config = fuel_many?FUEL:FUEL_OFF
            ULD_OUT_MANY.config = !fuel_many?FUEL:FUEL_OFF
    end
end

@cardillan
Copy link
Owner

That's a very good idea! I might just drop the explicit grouping of the values, so my syntax would look something like this (if it turns out to be easier to implement in the grammar):

foreach sort, conv in (SORT_1, CONV_1, SORT_2, CONV_2, SORT_3, CONV_3)
    item_type = sort.config
    item_count = CONTAINER.sensor(item_type)
    under_limit = item_count < QTY_LIMIT
    conv.enabled = under_limit
end

I'm not entirely sure I understand this:

Even nicer if there is a way to take output values from a loop and assign those to various values depending on index also.

Generally, working with indexes is troublesome in mlog. I do plan to add memory-backed arrays eventually. For the time being, I plan to add a yield keyword to the language, which would be used to modify the value being iterated over from within the loop, assuming all values are actually variables - something like this:

for i in (a, b, c)
    yield 1
end

This would set all variables to 1. In case of loops with several foreach variables we would need to specify which value are we assigning to, so maybe

for i, j  in (a1, a2, b1, b2, c1, c2)
    yield i = rand(5)
    yield j = 2 * i
end

Would this satisfy your needs?

@tom-leys
Copy link
Author

tom-leys commented Sep 5, 2024

Awesome! The yield idea does indeed satisfy my take output values from a loop requirement.

I was expecting when I suggested in the foreach loop that these loops must be unrolled (this is the best way I can think of to get/set those values in mlog).

You could potentially lift the loop body into a compiler generated "function" and then potentially trigger the standard "inline" optimisation if the body is small.

Instead of yield (is that a ruby thing?) you might consider the C syntax i* = rand(5) (treat it like a pointer) or just auto-detect that you are treating i like a variable. Potentially

for i, &j  in (a1, a2, b1, b2, c1, c2)
    i = rand(5) // Local (not &)
    j = 2 * i // modifies a2, b2 or c2
end

or

for local i, j  in (a1, a2, b1, b2, c1, c2)
    i = rand(5) // Creates a temporary for this loop only (local)
    j = 2 * i // modifies a2, b2 or c2
end

@tom-leys
Copy link
Author

tom-leys commented Sep 5, 2024

Oh, having reviewed #138 I think you should use the same syntax

for i, out j  in (a1, a2, b1, b2, c1, c2)
    i = rand(5) // Creates a temporary for this loop only 
    j = 2 * i // modifies a2, b2 or c2 (due to out)
end

@cardillan
Copy link
Owner

Oh, having reviewed #138 I think you should use the same syntax

for i, out j  in (a1, a2, b1, b2, c1, c2)
    i = rand(5) // Creates a temporary for this loop only 
    j = 2 * i // modifies a2, b2 or c2 (due to out)
end

This is an excellent idea - much better than yield! (Yield would be problematic - I'd probably need an extra variable to hold the result of the last yield in case the loop control variable was reassigned separately not using yield). Any assignments to an out foreach variable will be forwarded to the original variable after the entire loop body is executed.

Also, when I mentioned I want to have memory-backed arrays, I actually meant processor-variables backed arrays. And yes, these would rock with loop unrolling :)

@tom-leys
Copy link
Author

tom-leys commented Sep 6, 2024

Woah, it would really be something to have arrays without needing a memory processor thingie. I miss array access. I can see how that would be possible with unrolling or a syntax-sugar case statement.

@cardillan
Copy link
Owner

This is now covered by #142.

@tom-leys
Copy link
Author

tom-leys commented Sep 15, 2024

Also, when I mentioned I want to have memory-backed arrays, I actually meant processor-variables backed arrays. And yes, these would rock with loop unrolling :)

Hey, you could achieve arrays by creating a set of assignments followed by jumps. This is kind of pseudocode but like this

SET __arrayname_0 __acc
JMP __arr_ret
SET __arrayname_1 __acc
JMP __arr_ret
.. Repeat up to array size

And then you write into this array by loading __acc with a value to write, putting @counter+1 into __arr_ret and computing a jump into this block of code and putting that jump into @counter.

Another block of similar code deals with reads into _acc and now you have an array where array size * 4 determines the instruction overhead of the array.

And if unrolling means you know the array index as a constant, you can just write or read directly from __arrayname_1 say

@cardillan
Copy link
Owner

cardillan commented Sep 15, 2024

Yes, that's pretty much it. I just need to think of a good way of representing these operations in the pseudocode, so that the DFO can easily replace an access to a dynamic index with direct variable when the index is resolved as static.

By the way, mlogjs already has these arrays. Not the optimization, though, as far as I'm aware. (Edit: it has the optimization for simple cases. It doesn't have loop unrolling and a general data flow optimization, I think).

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

No branches or pull requests

2 participants