### ----------------------------------------------------
### -- MoarVM::Bytecode
### -- Licenses: Artistic-2.0
### -- Authors: Elizabeth Mattijsen
### -- File: lib/MoarVM/Ops.rakumod
### ----------------------------------------------------
### -- Chunk 4 of 4
 w(uint64) r(obj) int16 :pure',
  'sp_get_u8', '.s w(uint64) r(obj) int16 :pure',
  'sp_get_n', '.s w(num64) r(obj) int16 :pure',
  'sp_get_s', '.s w(str) r(obj) int16 :pure',
  'sp_bind_o', '.s r(obj) int16 r(obj)',
  'sp_bind_i64', '.s r(obj) int16 r(int64)',
  'sp_bind_i32', '.s r(obj) int16 r(int64)',
  'sp_bind_i16', '.s r(obj) int16 r(int64)',
  'sp_bind_i8', '.s r(obj) int16 r(int64)',
  'sp_bind_u64', '.s r(obj) int16 r(uint64)',
  'sp_bind_u32', '.s r(obj) int16 r(uint64)',
  'sp_bind_u16', '.s r(obj) int16 r(uint64)',
  'sp_bind_u8', '.s r(obj) int16 r(uint64)',
  'sp_bind_n', '.s r(obj) int16 r(num64)',
  'sp_bind_s', '.s r(obj) int16 r(str)',
  'sp_bind_s_nowb', '.s r(obj) int16 r(str)',
  'sp_p6oget_o', '.s w(obj) r(obj) int16 :pure',
  'sp_p6ogetvt_o', '.s w(obj) r(obj) int16 sslot',
  'sp_p6ogetvc_o', '.s w(obj) r(obj) int16 sslot',
  'sp_p6oget_i', '.s w(int64) r(obj) int16 :pure',
  'sp_p6oget_u', '.s w(uint64) r(obj) int16 :pure',
  'sp_p6oget_n', '.s w(num64) r(obj) int16 :pure',
  'sp_p6oget_s', '.s w(str) r(obj) int16 :pure',
  'sp_p6oget_bi', '.s w(int64) r(obj) int16 :pure',
  'sp_p6obind_o', '.s r(obj) int16 r(obj)',
  'sp_p6obind_i', '.s r(obj) int16 r(int64)',
  'sp_p6obind_u', '.s r(obj) int16 r(uint64)',
  'sp_p6obind_n', '.s r(obj) int16 r(num64)',
  'sp_p6obind_s', '.s r(obj) int16 r(str)',
  'sp_p6oget_i32', '.s w(int64) r(obj) int16 :pure',
  'sp_p6oget_u32', '.s w(uint64) r(obj) int16 :pure',
  'sp_p6obind_i32', '.s r(obj) int16 r(int64) :pure',
  'sp_p6obind_u32', '.s r(obj) int16 r(uint64) :pure',
  'sp_getvt_o', '.s w(obj) r(obj) int16 sslot',
  'sp_getvc_o', '.s w(obj) r(obj) int16 sslot',
  'sp_fastbox_i', '.s w(obj) int16 sslot int16 r(int64) :pure',
  'sp_fastbox_u', '.s w(obj) int16 sslot int16 r(uint64) :pure',
  'sp_fastbox_bi', '.s w(obj) int16 sslot int16 r(int64) :pure',
  'sp_fastbox_i_ic', '.s w(obj) int16 sslot int16 r(int64) int16 :pure',
  'sp_fastbox_u_ic', '.s w(obj) int16 sslot int16 r(uint64) int16 :pure',
  'sp_fastbox_bi_ic', '.s w(obj) int16 sslot int16 r(int64) int16 :pure',
  'sp_deref_get_i64', '.s w(int64) r(obj) int16 :pure',
  'sp_deref_get_n', '.s w(num64) r(obj) int16 :pure',
  'sp_deref_bind_i64', '.s r(obj) r(int64) int16',
  'sp_deref_bind_n', '.s r(obj) r(num64) int16',
  'sp_getlexvia_o', '.s w(obj) int16 int16 r(obj) :pure',
  'sp_getlexvia_ins', '.s w(`1) int16 int16 r(obj) :pure',
  'sp_bindlexvia_os', '.s int16 int16 r(obj) r(obj) :pure',
  'sp_bindlexvia_in', '.s int16 int16 r(obj) r(obj) :pure',
  'sp_getstringfrom', '.s w(str) sslot int32 :pure',
  'sp_getwvalfrom', '.s w(obj) sslot int64 :pure',
  'sp_jit_enter', '.s w(obj)',
  'sp_istrue_n', '.s w(int64) r(num64) :pure',
  'sp_boolify_iter', '.s w(int64) r(obj) :pure',
  'sp_boolify_iter_arr', '.s w(int64) r(obj) :pure',
  'sp_boolify_iter_hash', '.s w(int64) r(obj) :pure',
  'sp_cas_o', '.s w(obj) r(obj) r(obj) r(obj) :invokish :maycausedeopt',
  'sp_atomicload_o', '.s w(obj) r(obj)',
  'sp_atomicstore_o', '.s r(obj) r(obj) :invokish :maycausedeopt',
  'sp_add_I', '.s w(obj) int16 sslot r(obj) r(obj) int16 int16 :pure',
  'sp_sub_I', '.s w(obj) int16 sslot r(obj) r(obj) int16 int16 :pure',
  'sp_mul_I', '.s w(obj) int16 sslot r(obj) r(obj) int16 int16 :pure',
  'sp_bool_I', '.s w(int64) r(obj) int16 :pure',
  'sp_runbytecode_v', '.s r(obj) uint64 int16 :maycausedeopt :invokish',
  'sp_runbytecode_i', '.s w(int64) r(obj) uint64 int16 :maycausedeopt :invokish',
  'sp_runbytecode_u', '.s w(uint64) r(obj) uint64 int16 :maycausedeopt :invokish',
  'sp_runbytecode_n', '.s w(num64) r(obj) uint64 int16 :maycausedeopt :invokish',
  'sp_runbytecode_s', '.s w(str) r(obj) uint64 int16 :maycausedeopt :invokish',
  'sp_runbytecode_o', '.s w(obj) r(obj) uint64 int16 :maycausedeopt :invokish',
  'sp_runcfunc_v', '.s r(obj) uint64',
  'sp_runcfunc_i', '.s w(int64) r(obj) uint64',
  'sp_runcfunc_u', '.s w(uint64) r(obj) uint64',
  'sp_runcfunc_n', '.s w(num64) r(obj) uint64',
  'sp_runcfunc_s', '.s w(str) r(obj) uint64',
  'sp_runcfunc_o', '.s w(obj) r(obj) uint64',
  'sp_runnativecall_v', '.s r(obj) uint64 :maycausedeopt :invokish',
  'sp_runnativecall_i', '.s w(int64) r(obj) uint64 :maycausedeopt :invokish',
  'sp_runnativecall_u', '.s w(uint64) r(obj) uint64 :maycausedeopt :invokish',
  'sp_runnativecall_n', '.s w(num64) r(obj) uint64 :maycausedeopt :invokish',
  'sp_runnativecall_s', '.s w(str) r(obj) uint64 :maycausedeopt :invokish',
  'sp_runnativecall_o', '.s w(obj) r(obj) uint64 :maycausedeopt :invokish',
  'sp_resumption', '.s w(obj) uint16 uint16',
  'prof_enter', '.s',
  'prof_enterspesh', '.s',
  'prof_enterinline', '.s sslot',
  'prof_enternative', '.s r(obj)',
  'prof_exit', '.s',
  'prof_allocated', '.s r(obj)',
  'prof_replaced', '.s sslot',
  'ctw_check', '.s r(obj) int16',
  'coverage_log', '.s str int32 int32 int64',
  'breakpoint', '.s int32 int32 :throwish :invokish',
;
#- PLEASE DON'T CHANGE ANYTHING ABOVE THIS LINE
#- end of generated part of Ops  -----------------------------------------------

my constant %op2index = @ops.kv.reverse;

# The first item on each line, can be a prefix
my constant %annotation2name =
  "d", "dispatch",
  "j", "jump",
  "p", "parameter",
  "r", "return",
  "s", "spesh", 
;

my constant @adverbs = %ops.values.map( {
    (quietly .head.starts-with("." | ":") ?? .skip !! $_)
      .map({ .substr(1) if .starts-with(":") }).Slip
} ).unique.sort;

# Since most Op objects are singletons, use this as a compile time store.
# If the number of bytes can not be determined at compile time, it will
# be set to zero.
my @reified-ops;

#-------------------------------------------------------------------------------
# Front end class for the MoarVM operator information
class MoarVM::Op {
    has str  $.name;
    has uint $.index;
    has uint $.bytes;
    has str  $.annotation;
    has Bool $.is-sequence is built(:bind);
    has List $.operands    is built(:bind);
    has Map  $.adverbs     is built(:bind);

    proto method new(|) {*}
    multi method new(Int:D $index) {
        @reified-ops[$index]
          // "No op known at index $index: must be between 0 and @reified-ops.end()".Failure
    }
    multi method new(Str:D $name) {
        my $index := %op2index{$name} // fail "No op known with name '$name'";
        @reified-ops[$index]
    }

    # The number of bytes an opcode takes *can* be dependent on the
    # callsite, so can only be reliably calculated if the frame's
    # callsites are also supplied.  As we don't have that here and
    # now, reset to 0 bytes to indicate it's non-conclusive.
    sub calculate-bytes(@operands, &variable-handler = -> $ { return 0 }) {
        my uint $bytes = 2;
        for @operands {
            $bytes = $bytes + (.starts-with('r(')
              || .starts-with('w(')
              || .ends-with('8')
              || .ends-with('16')
              || $_ eq 'coderef'
              || $_ eq 'sslot'
              ?? 2
              !! .starts-with('rl(')
                || .starts-with('wl(')
                || .ends-with('32')
                || $_ eq 'ins'
                || $_ eq 'str'
                ?? 4
                !! .ends-with('64')
                  ?? 8
                  !! $_ eq 'callsite'
                    ?? variable-handler($bytes)
                    !! die "don't know how to handle '$_'"
            )
        }

        $bytes
    }

    method instantiate($index, $name) {
        my @info = %ops{$name}.words;

        my $annotation  := "";
        my $is-sequence := False;
        if @info.head -> $head {
            if ($head.starts-with(".") || $head.starts-with(":"))
              && %annotation2name{$head.substr(1)} -> $_ {
                $annotation  := $_;
                $is-sequence := $head.starts-with(":");
                @info.shift;
            }

            # DEPRECATED ops may have these annotations
            elsif $head.starts-with('+')
              || $head.starts-with('-')
              || $head.starts-with('*') {
                @info.shift;  # ignore
            }
        }

        my @operands is List = @info.grep(!*.starts-with(":")).Slip;
        my %adverbs  is Map  = @info.map: {
            .substr(1) => True if .starts-with(":")
        }

        # The number of bytes an opcode takes *can* be dependent on the
        # callsite, so can only be reliably calculated if the frame's
        # callsites are also supplied.  As we don't have that here and
        # now, reset to 0 bytes to indicate it's non-conclusive.
        my uint $bytes = calculate-bytes(@operands);

        self.bless(
          :$name, :$index, :$bytes, :$annotation, :$is-sequence,
          :@operands, :%adverbs
        )
    }

    method not-inlineable(MoarVM::Op:D:) { $!adverbs<noinline> }

    method bytes(MoarVM::Op:D: $source?, $offset?) {
        $!bytes || calculate-bytes $!operands, -> $bytes {
            2 + $source.callsites[
              $source.opcodes.read-uint16($offset + $bytes, LittleEndian)
            ].bytes
        }
    }

    multi method gist(MoarVM::Op:D: :$verbose) {
        my str @parts = $!index.fmt('%4x'), $!name.fmt('%-16s');

        @parts.push($!operands.join(' '))
          if $!operands.elems;
        @parts.push($!is-sequence ?? "[:$!annotation]" !! "[.$!annotation]")
          if $!annotation;
        @parts.push: $!bytes ?? "($!bytes bytes)" !! "(variable length)"
          if $verbose;

        @parts.join(' ')
    }
    multi method gist(MoarVM::Op:D: Buf:D $bytecode) {
        die "Improperly formatted bytecode"
          if $bytecode.read-uint16(0, LittleEndian) != $!index;

        ""
    }

    method all-adverbs() { @adverbs     }
    method all-ops()     { @reified-ops }
}

# Make sure we have singletons for all ops
BEGIN {
    for @ops.kv -> $index, $name {
        @reified-ops[$index] := MoarVM::Op.instantiate($index, $name);
    }
}

# vim: expandtab shiftwidth=4


