### ----------------------------------------------------
### -- Wasm::Emitter
### -- Licenses: Artistic-2.0
### -- Authors: Jonathan Worthington <jnthn@jnthn.net>
### -- File: t/expressions.rakutest
### ----------------------------------------------------
### -- Chunk 2 of 2
FFFFF00 -> $suffix, $expected {
        subtest "i64.store$suffix" => {
            my $emitter = Wasm::Emitter.new;
            $emitter.memory(limitstype(1));
            my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i64()));
            my $expression = Wasm::Emitter::Expression.new;
            my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
            $expression.i32-const(1);
            $expression.i64-const(0xFFFFFFFFFF);
            $expression."i64-store$suffix"(:0align);
            $expression.i32-const(0);
            $expression.i64-load();
            $emitter.export-function('test', $emitter.function($function));
            my $buf = $emitter.assemble();
            pass 'Assembled module';
            is-function-output $buf, [], $expected;
        }
    }

    subtest 'memory.size' => {
        my $emitter = Wasm::Emitter.new;
        $emitter.memory(limitstype(4));
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        $expression.memory-size();
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 4;
    }

    subtest 'memory.grow' => {
        my $emitter = Wasm::Emitter.new;
        $emitter.memory(limitstype(4));
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        $expression.i32-const(2);
        $expression.memory-grow();
        $expression.drop();
        $expression.memory-size();
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 6;
    }

    subtest 'memory.fill, memory.copy' => {
        my $emitter = Wasm::Emitter.new;
        $emitter.memory(limitstype(1));
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i64()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        $expression.i32-const(1);
        $expression.i32-const(0xFF);
        $expression.i32-const(2);
        $expression.memory-fill();
        $expression.i32-const(4);
        $expression.i32-const(0);
        $expression.i32-const(4);
        $expression.memory-copy();
        $expression.i32-const(0);
        $expression.i64-load();
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 0x00FFFF00_00FFFF00;
    }

    subtest 'memory.init, data.drop' => {
        my $emitter = Wasm::Emitter.new;
        $emitter.memory(limitstype(1));
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        my $data-idx = $emitter.passive-data(Buf.new(0xFE, 0xCA));
        $expression.i32-const(1);
        $expression.i32-const(0);
        $expression.i32-const(2);
        $expression.memory-init($data-idx);
        $expression.data-drop($data-idx);
        $expression.i32-const(0);
        $expression.i32-load();
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 0x00CAFE00;
    }

    subtest 'global.get, global.set' => {
        my $emitter = Wasm::Emitter.new;
        my $init-expression = Wasm::Emitter::Expression.new;
        $init-expression.i32-const(2);
        my $global-idx = $emitter.global(globaltype(i32(), :mutable), $init-expression);
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        $expression.global-get($global-idx);
        $expression.i32-const(3);
        $expression.global-set($global-idx);
        $expression.global-get($global-idx);
        $expression.i32-mul();
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 2 * 3;
    }

    subtest 'ref.null and ref.is_null' => {
        my $emitter = Wasm::Emitter.new;
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        $expression.ref-null(funcref());
        $expression.ref-is-null();
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 1;
    }

    subtest 'ref.func and ref.is_null' => {
        my $emitter = Wasm::Emitter.new;
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        my $func-idx = $emitter.function($function);
        $expression.ref-func($func-idx);
        $expression.ref-is-null();
        $emitter.export-function('test', $func-idx);
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 0;
    }

    subtest 'table.get' => {
        my $emitter = Wasm::Emitter.new;
        my $table-idx = $emitter.table(tabletype(limitstype(8), externref()));
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        $expression.i32-const(0);
        $expression.table-get($table-idx);
        $expression.ref-is-null();
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 1;
    }

    subtest 'table.set' => {
        my $emitter = Wasm::Emitter.new;
        my $table-idx = $emitter.table(tabletype(limitstype(8), funcref()));
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        my $func-idx = $emitter.function($function);
        $expression.i32-const(0);
        $expression.ref-func($func-idx);
        $expression.table-set($table-idx);
        $expression.i32-const(0);
        $expression.table-get($table-idx);
        $expression.ref-is-null();
        $emitter.export-function('test', $func-idx);
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 0;
    }

    subtest 'table.size and table.grow' => {
        my $emitter = Wasm::Emitter.new;
        my $table-idx = $emitter.table(tabletype(limitstype(8), externref()));
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        $expression.table-size($table-idx);
        $expression.ref-null(externref());
        $expression.i32-const(4);
        $expression.table-grow($table-idx);
        $expression.drop();
        $expression.table-size($table-idx);
        $expression.i32-add();
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 8 + 12;
    }

    subtest 'table.fill and table.copy' => {
        my $emitter = Wasm::Emitter.new;
        my $table-idx-a = $emitter.table(tabletype(limitstype(8), funcref()));
        my $table-idx-b = $emitter.table(tabletype(limitstype(8), funcref()));
        my $type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:$type-index, :$expression);
        my $func-idx = $emitter.function($function);
        $expression.i32-const(0);
        $expression.ref-func($func-idx);
        $expression.i32-const(4);
        $expression.table-fill($table-idx-a);
        $expression.i32-const(2);
        $expression.i32-const(0);
        $expression.i32-const(4);
        $expression.table-copy($table-idx-b, $table-idx-a);
        $expression.i32-const(5);
        $expression.table-get($table-idx-b);
        $expression.ref-is-null();
        $emitter.export-function('test', $func-idx);
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [], 0;
    }

    subtest 'call_indirect, table constructed from functions in globals' => {
        my $emitter = Wasm::Emitter.new;
        my $table-idx = $emitter.table(tabletype(limitstype(4), funcref()));
        my $callee-type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $caller-type-index = $emitter.function-type: functype(resulttype(i32()), resulttype(i32()));
        # Declare four functions returning integers. We also have to put them
        # into some kind of declaration to make them possible to reference;
        # use a global for that.
        my @funcs = do for ^4 {
            my $expression = Wasm::Emitter::Expression.new;
            my $function = Wasm::Emitter::Function.new(:type-index($callee-type-index), :$expression);
            $expression.i32-const(4 * $_);
            my $func-index = $emitter.function($function);
            my $ref-expression = Wasm::Emitter::Expression.new;
            $ref-expression.ref-func($func-index);
            $emitter.global(globaltype(funcref()), $ref-expression);
            $func-index
        }
        # Put them into a table, then use it for call_indirect testing.
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:type-index($caller-type-index), :1parameters, :$expression);
        for ^4 {
            $expression.i32-const($_);
            $expression.ref-func(@funcs[$_]);
            $expression.table-set($table-idx);
        }
        $expression.local-get(0);
        $expression.call-indirect($callee-type-index, $table-idx);
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [0], 0;
        is-function-output $buf, [1], 4;
        is-function-output $buf, [2], 8;
        is-function-output $buf, [3], 12;
    }

    subtest 'call_indirect, table constructed from functions in declarative elements' => {
        my $emitter = Wasm::Emitter.new;
        my $table-idx = $emitter.table(tabletype(limitstype(4), funcref()));
        my $callee-type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $caller-type-index = $emitter.function-type: functype(resulttype(i32()), resulttype(i32()));
        # Declare four functions returning integers, using declarative
        # elements to declare them up-front.
        my @indices;
        my @ref-exprs;
        for ^4 {
            my $expression = Wasm::Emitter::Expression.new;
            my $function = Wasm::Emitter::Function.new(:type-index($callee-type-index), :$expression);
            $expression.i32-const(4 * $_);
            my $func-index = $emitter.function($function);
            my $ref-expression = Wasm::Emitter::Expression.new;
            $ref-expression.ref-func($func-index);
            @indices.push($func-index);
            @ref-exprs.push($ref-expression);
        }
        $emitter.elements(Wasm::Emitter::Elements::Declarative.new(:type(funcref()), :init(@ref-exprs)));
        # Put them into a table, then use it for call_indirect testing.
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:type-index($caller-type-index), :1parameters, :$expression);
        for ^4 {
            $expression.i32-const($_);
            $expression.ref-func(@indices[$_]);
            $expression.table-set($table-idx);
        }
        $expression.local-get(0);
        $expression.call-indirect($callee-type-index, $table-idx);
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [0], 0;
        is-function-output $buf, [1], 4;
        is-function-output $buf, [2], 8;
        is-function-output $buf, [3], 12;
    }

    subtest 'call_indirect, table populated by active elements' => {
        my $emitter = Wasm::Emitter.new;
        my $table-idx = $emitter.table(tabletype(limitstype(4), funcref()));
        my $callee-type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $caller-type-index = $emitter.function-type: functype(resulttype(i32()), resulttype(i32()));
        # Declare four functions returning integers, and add an active
        # elements section.
        my @indices;
        my @ref-exprs;
        for ^4 {
            my $expression = Wasm::Emitter::Expression.new;
            my $function = Wasm::Emitter::Function.new(:type-index($callee-type-index), :$expression);
            $expression.i32-const(4 * $_);
            my $func-index = $emitter.function($function);
            my $ref-expression = Wasm::Emitter::Expression.new;
            $ref-expression.ref-func($func-index);
            @indices.push($func-index);
            @ref-exprs.push($ref-expression);
        }
        my $offset = Wasm::Emitter::Expression.new;
        $offset.i32-const(0);
        $emitter.elements: Wasm::Emitter::Elements::Active.new:
                :type(funcref()), :init(@ref-exprs),
                :table-index($table-idx), :$offset;
        # Use table use it for call_indirect testing.
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:type-index($caller-type-index), :1parameters, :$expression);
        $expression.local-get(0);
        $expression.call-indirect($callee-type-index, $table-idx);
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [0], 0;
        is-function-output $buf, [1], 4;
        is-function-output $buf, [2], 8;
        is-function-output $buf, [3], 12;
    }

    subtest 'call_indirect, table constructed from functions loaded from passive elements' => {
        my $emitter = Wasm::Emitter.new;
        my $table-idx = $emitter.table(tabletype(limitstype(4), funcref()));
        my $callee-type-index = $emitter.function-type: functype(resulttype(), resulttype(i32()));
        my $caller-type-index = $emitter.function-type: functype(resulttype(i32()), resulttype(i32()));
        # Declare four functions returning integers, using declarative
        # elements to declare them up-front.
        my @indices;
        my @ref-exprs;
        for ^4 {
            my $expression = Wasm::Emitter::Expression.new;
            my $function = Wasm::Emitter::Function.new(:type-index($callee-type-index), :$expression);
            $expression.i32-const(4 * $_);
            my $func-index = $emitter.function($function);
            my $ref-expression = Wasm::Emitter::Expression.new;
            $ref-expression.ref-func($func-index);
            @indices.push($func-index);
            @ref-exprs.push($ref-expression);
        }
        my $elements-idx = $emitter.elements: Wasm::Emitter::Elements::Passive.new:
                :type(funcref()), :init(@ref-exprs);
        # Load elements into a table, then use it for call_indirect testing.
        my $expression = Wasm::Emitter::Expression.new;
        my $function = Wasm::Emitter::Function.new(:type-index($caller-type-index), :1parameters, :$expression);
        $expression.i32-const(0);
        $expression.i32-const(0);
        $expression.i32-const(4);
        $expression.table-init($elements-idx, $table-idx);
        $expression.elem-drop($elements-idx);
        $expression.local-get(0);
        $expression.call-indirect($callee-type-index, $table-idx);
        $emitter.export-function('test', $emitter.function($function));
        my $buf = $emitter.assemble();
        pass 'Assembled module';
        is-function-output $buf, [0], 0;
        is-function-output $buf, [1], 4;
        is-function-output $buf, [2], 8;
        is-function-output $buf, [3], 12;
    }
}
else {
    skip 'No wasmtime available to run test output; skipping';
}

done-testing;


