Ticket #1432 (closed RFC: wontfix)

Opened 4 years ago

Last modified 4 years ago

Remove 2-argument math ops

Reported by: whiteknight Owned by:
Priority: normal Milestone:
Component: core Version: 2.0.0
Severity: medium Keywords:
Cc: Language:
Patch status: Platform:

Description

I would like to propose a removal of the various 2-argument math ops. These are the "in place ops", where:

foo a, b

performs this operation:

a = a foo b

And for many, corresponds to the symbolic operator:

a foo= b

To my knowledge, all two-argument ops can be more easily rewritten in terms of three-argument ops. In fact, most 2-argument ops are already implemented like this internally.

a = foo a, b

These 2-argument forms don't provide any real benefit because they perform the same operations internally as the 3-argument variants, plus they add significant bulk to the in-memory list of ops and create a maintenance burden when they need to be updated. The only benefit that I can see is a small amount of programmer convenience for people who want to use them but don't want to use the "op=" symbolic ops (which can easily be remapped to the 3-argument forms inside IMCC).

I would like to put in a deprecation notice for these ops and remove them at the earliest possible point.

Change History

in reply to: ↑ description   Changed 4 years ago by pmichaud

Replying to whiteknight:

These 2-argument forms don't provide any real benefit because they perform the same operations internally as the 3-argument variants, plus they add significant bulk to the in-memory list of ops and create a maintenance burden when they need to be updated. The only benefit that I can see is a small amount of programmer convenience for people who want to use them but don't want to use the "op=" symbolic ops (which can easily be remapped to the 3-argument forms inside IMCC).

The traditional benefit from the 2-argument forms is that they can be significantly optimized -- in particular, there's a *huge* benefit that the 2-argument form doesn't require creating a new PMC, whereas the 3-argument form always creates a new PMC. (The fact that Parrot may not be taking advantage of this potential optimization yet doesn't mean that languages will not eventually want to do so.)

Also note that "foo a, b" is _not_ really the same thing as "a = foo a, b", as the latter form rebinds the a register (leaving the original PMC unchanged) as opposed to modifying a PMC in place. Consider the differences:

    $P0 = hash['foo']
    add $P0, 4            # changes hash['foo']

    $P1 = hash['bar']
    $P1 = add $P1, 4      # hash['bar'] unchanged

So, until the assignment/binding semantics are worked out a bit better in Parrot, and until we know what other languages are likely to want in their PMC types, I wouldn't target the 2-argument forms for elimination just yet.

Pm

follow-up: ↓ 3   Changed 4 years ago by whiteknight

Okay, that's a fair critique. I would suggest that because the assignment happens internal to the op that it could detect when the output is the same as the input and not rebind. In other words there is no concrete reason we always need to create a new PMC, so long as we pick one way to do it and are consistent.

At the algorithmic level, systems that make consistant use of 3-arg ops (like MIPS) have better optimization opportunities than systems that make almost-exclusive use of two-arg ops (like x86). A lot of systems like LLVM use 3-arg ops internally for this reason, and if Parrot wasusing them as well translation to those ops becomes easier.

This maybe raises a more pertinent question: What will it take to sort out problems with assignment/binding semantics?

in reply to: ↑ 2   Changed 4 years ago by pmichaud

Replying to whiteknight:

Okay, that's a fair critique. I would suggest that because the assignment happens internal to the op that it could detect when the output is the same as the input and not rebind. [...]

I fear that this down this path lies madness. Are you proposing (a) the vtable function performs an inplace operation whenever the output and input PMCs are the same, or (b) an opcode switches between inplace and normal vtable functions whenever the output and input registers are the same? Choice (a) is almost certainly wrong in a large number of cases. I don't see any advantage to (b) -- perhaps we reduce the number of opcodes, but we add a fair bit of complexity to any PIR compiler or other code generators.

This maybe raises a more pertinent question: What will it take to sort out problems with assignment/binding semantics?

After thinking about this a bit, I'm not at all optimistic that this will happen anytime soon, if ever.

Sorting out assignment/binding semantics reminds me of the problems we had with sorting out lexicals, except that the existing codebase will make fixing assignment/binding about 2-5 times harder to resolve. In the case of lexicals, many of us kept punting the issues for a couple of years until someone finally decided to really invest the time/energy needed to get a deep understanding of lexicals and then get a working implementation in Parrot. I feel the same is true for assignment/binding -- sorting it out will require someone with (or that can acquire) a really deep understanding of assignment/binding and how Parrot achieves each. And, as with lexicals, I don't see that we have anyone that is particularly eager to tackle the problem.

And if that's not bad enough, fixing lexicals was made easier by the fact that it was fairly well-encapsulated in the code and there was little existing code that depended on any broken semantics. This is definitely not the case for assignment/binding -- the issues there affect the fundamental design and implementation of core vtable functions and PMCs, and a lot of code and libraries depend heavily on the current design (or mis-design, depending on your POV).

Perhaps the above is overly pessimistic, but having dealt with both areas over the past 2-3 years, I'm guessing that (1) assignment/binding will be a lot harder to redesign and fix than lexicals, and (2) as with lexicals, there's not anyone likely to be taking ownership of the problem anytime soon.

Pm

follow-up: ↓ 6   Changed 4 years ago by kurahaupo

My take is that the assignment/binding problem seems so untractable mostly because we don't have a parrot-level distinction between a container and a (non-I/N/S) value.

If you can keep focussed on that distinction, binding and assignment seem a whole lot more straightforward. The really hard part is keeping everyone else also focussed on it.

Some blue-sky options:

  • split the "P" registers into two types: "C" (container) and "V" (value); or
  • split PObj into PCon and PVal.

Having done either of those, it's quite plain that the choice of reuse-the-PMC-or-not depends not on the number of args, but on whether the output register (or PMC) is a "C" or "V" type (or the target PMC is a PCon or a PVal).

Thus you get either:

Vx = op Vy, Vz

or:

Cx op= Vz

Coming back to less radical suggestions:

  • if the output P register of a 3-arg op is also one of its input registers, assume that the PMC it refers to is available for recycling. That leaves the method the option of doing in-place modification, or generating a new PMC, which ever fits its model best.
  • to facilitate automated microthreading, always apply strict "value" semantics to the 3-arg op.

  Changed 4 years ago by kurahaupo

I note that in #1434 you suggest that the 3-arg form should always return a fresh PMC. (I would agree, as this greatly facilitates autothreading.)

in reply to: ↑ 4 ; follow-up: ↓ 7   Changed 4 years ago by pmichaud

* if the output P register of a 3-arg op is also one of its input registers, assume that the PMC it refers to is available for recycling. That leaves the method the option of doing in-place modification, or generating a new PMC, which ever fits its model best.

I strongly disagree. First, the statement confuses opcodes (which can detect identical registers) with PMC methods (which can detect identical PMCs, but not identical registers).

In short, if the "method" looks at source and destination PMCs to select inplace modification, then one cannot look at a simple instruction like

$P0 = add $P1, 4

and definitively know if it's an inplace modification or not. What if $P0 and $P1 happen to refer to the same PMC (perhaps $P1 has a value from a previous loop iteration), but the intent is to always create a new value and leave $P1 alone?

Pm

in reply to: ↑ 6   Changed 4 years ago by kurahaupo

Replying to pmichaud:

* if the output P register of a 3-arg op is also one of its input registers, assume that the PMC it refers to is available for recycling. That leaves the method the option of doing in-place modification, or generating a new PMC, which ever fits its model best.

I strongly disagree. First, the statement confuses opcodes (which can detect identical registers) with PMC methods (which can detect identical PMCs, but not identical registers).

Fair point,although I thought we were discussing ops, not methods. (And in the case where an op invokes a method, an op could choose between in-place and return-value methods.)

... What if $P0 and $P1 happen to refer to the same PMC (perhaps $P1 has a value from a previous loop iteration), but the intent is to always create a new value and leave $P1 alone?

What I had in mind was simply checking whether the same register was used for both, at least for ops.

However another way to express that intent would be along the lines of splitting "PObj" into "PCon" and "PVal", even if it's just notionally by a flag bit. In other words, if the target PMC expresses (somehow) "I am an immutable value", then obviously it's not a container you can assign to. Conversely if it says "I am a container" (or such is inferred by lack of immutability) then in-place modification is an option.

My point really is that in-place modification should only be an option, not mandatory: the HLL should not assume that the P register holds the same value as beforehand.

  Changed 4 years ago by jkeenan

Commenters,

Are we going to take any action on the issues raised in this ticket?

If not, are we going to close it as rejected?

Thank you very much.

kid51

  Changed 4 years ago by jkeenan

  • component changed from none to core

  Changed 4 years ago by whiteknight

  • status changed from new to closed
  • resolution set to wontfix

I am going to mark this ticket as rejected. I don't think we are going to deprecate these ops any time soon (and hopefully Lorito will make most of these issues disappear or change dramatically anyway), so there's no sense worrying about it now. Most of the comments in this ticket did seem to discuss another possible idea, that the notion of a PMC value and a PMC container are really different and that we should do more to separate them. If we want to pursue that issue, it should be done in a different ticket.

Note: See TracTickets for help on using tickets.