/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

class MultipleObject {
    Object inner;
    Object inner2;

    static Object inner_static;
}

public class Main {
    public static void main(String[] args) throws Error {
        // Several sets, same receiver.
        $noinline$testInstanceFieldSets(new Main(), new Object(), new Object(), new Object());
        $noinline$testStaticFieldSets(new Object(), new Object(), new Object());
        // Object ArraySets can throw since they need a type check so we cannot perform the
        // optimization.
        $noinline$testArraySets(new Object[3], new Object(), new Object(), new Object());
        // If we are swapping elements in the array, no need for a type check.
        $noinline$testSwapArray(new Object[3]);
        // If the array and the values have the same RTI, no need for a type check.
        $noinline$testArraySetsSameRTI();

        // We cannot rely on `null` sets to perform the optimization.
        $noinline$testNullInstanceFieldSets(new Main(), new Object());
        $noinline$testNullStaticFieldSets(new Object());
        $noinline$testNullArraySets(new Object[3], new Object());

        // Several sets, multiple receivers. (set obj1, obj2, obj1 and see that the card of obj1
        // gets eliminated)
        $noinline$testInstanceFieldSetsMultipleReceivers(
                new Main(), new Object(), new Object(), new Object());
        $noinline$testStaticFieldSetsMultipleReceivers(new Object(), new Object(), new Object());
        $noinline$testArraySetsMultipleReceiversSameRTI();

        // The write barrier elimination optimization is blocked by invokes, suspend checks, and
        // instructions that can throw.
        $noinline$testInstanceFieldSetsBlocked(
                new Main(), new Object(), new Object(), new Object());
        $noinline$testStaticFieldSetsBlocked(new Object(), new Object(), new Object());
        $noinline$testArraySetsSameRTIBlocked();
    }

    /// CHECK-START: Main Main.$noinline$testInstanceFieldSets(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:EmitNoNullCheck
    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:DontEmit
    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:DontEmit

    /// CHECK-START: Main Main.$noinline$testInstanceFieldSets(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static Main $noinline$testInstanceFieldSets(Main m, Object o, Object o2, Object o3) {
        m.inner = o;
        m.inner2 = o2;
        m.inner3 = o3;
        return m;
    }

    /// CHECK-START: void Main.$noinline$testStaticFieldSets(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:EmitNoNullCheck
    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:DontEmit
    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit

    /// CHECK-START: void Main.$noinline$testStaticFieldSets(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static void $noinline$testStaticFieldSets(Object o, Object o2, Object o3) {
        inner_static = o;
        inner_static2 = o2;
        inner_static3 = o3;
    }

    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySets(java.lang.Object[], java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck

    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySets(java.lang.Object[], java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static java.lang.Object[] $noinline$testArraySets(
            Object[] arr, Object o, Object o2, Object o3) {
        arr[0] = o;
        arr[1] = o2;
        arr[2] = o3;
        return arr;
    }

    /// CHECK-START: java.lang.Object[] Main.$noinline$testSwapArray(java.lang.Object[]) disassembly (after)
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit

    /// CHECK-START: java.lang.Object[] Main.$noinline$testSwapArray(java.lang.Object[]) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static java.lang.Object[] $noinline$testSwapArray(Object[] arr) {
        arr[0] = arr[1];
        arr[1] = arr[2];
        arr[2] = arr[0];
        return arr;
    }

    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTI() disassembly (after)
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit

    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTI() disassembly (after)
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static java.lang.Object[] $noinline$testArraySetsSameRTI() {
        Object[] arr = new Object[3];
        arr[0] = inner_static;
        arr[1] = inner_static2;
        arr[2] = inner_static3;
        return arr;
    }

    /// CHECK-START: Main Main.$noinline$testNullInstanceFieldSets(Main, java.lang.Object) disassembly (after)
    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:DontEmit
    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:EmitWithNullCheck
    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:DontEmit

    /// CHECK-START: Main Main.$noinline$testNullInstanceFieldSets(Main, java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static Main $noinline$testNullInstanceFieldSets(Main m, Object o) {
        m.inner = null;
        m.inner2 = o;
        m.inner3 = null;
        return m;
    }

    /// CHECK-START: void Main.$noinline$testNullStaticFieldSets(java.lang.Object) disassembly (after)
    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:DontEmit
    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitWithNullCheck
    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit

    /// CHECK-START: void Main.$noinline$testNullStaticFieldSets(java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static void $noinline$testNullStaticFieldSets(Object o) {
        inner_static = null;
        inner_static2 = o;
        inner_static3 = null;
    }

    /// CHECK-START: java.lang.Object[] Main.$noinline$testNullArraySets(java.lang.Object[], java.lang.Object) disassembly (after)
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
    /// CHECK: ArraySet needs_type_check:true can_trigger_gc:true write_barrier_kind:EmitNoNullCheck
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit

    /// CHECK-START: java.lang.Object[] Main.$noinline$testNullArraySets(java.lang.Object[], java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static Object[] $noinline$testNullArraySets(Object[] arr, Object o) {
        arr[0] = null;
        arr[1] = o;
        arr[2] = null;
        return arr;
    }

    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsMultipleReceivers(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    // There are two extra card_tables for the initialization of the MultipleObject.
    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner field_type:Reference write_barrier_kind:EmitNoNullCheck
    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner field_type:Reference write_barrier_kind:EmitWithNullCheck
    /// CHECK: InstanceFieldSet field_name:MultipleObject.inner2 field_type:Reference write_barrier_kind:DontEmit

    // Each one of the two NewInstance instructions have their own `card_table` reference.
    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsMultipleReceivers(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static Main $noinline$testInstanceFieldSetsMultipleReceivers(
            Main m, Object o, Object o2, Object o3) throws Error {
        m.mo = new MultipleObject();
        m.mo2 = new MultipleObject();

        m.mo.inner = o;
        // This card table for `m.mo2` can't me removed. Note that in `m.mo2 = new
        // MultipleObject();` above the receiver is `m`, not `m.mo2.
        m.mo2.inner = o2;
        // This card table for `m.mo` can me removed.
        m.mo.inner2 = o3;
        return m;
    }

    /// CHECK-START: void Main.$noinline$testStaticFieldSetsMultipleReceivers(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: StaticFieldSet field_name:MultipleObject.inner_static field_type:Reference write_barrier_kind:EmitWithNullCheck
    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitNoNullCheck
    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:DontEmit

    /// CHECK-START: void Main.$noinline$testStaticFieldSetsMultipleReceivers(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static void $noinline$testStaticFieldSetsMultipleReceivers(
            Object o, Object o2, Object o3) {
        MultipleObject.inner_static = o;
        inner_static2 = o2;
        inner_static3 = o3;
    }

    /// CHECK-START: java.lang.Object[][] Main.$noinline$testArraySetsMultipleReceiversSameRTI() disassembly (after)
    // Initializing the values
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit
    // Setting the `array_of_arrays`.
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:DontEmit

    /// CHECK-START: java.lang.Object[][] Main.$noinline$testArraySetsMultipleReceiversSameRTI() disassembly (after)
    // Two array sets can't eliminate the write barrier
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    // One write barrier for the array of arrays' sets
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static java.lang.Object[][] $noinline$testArraySetsMultipleReceiversSameRTI() {
        Object[] arr = new Object[3];
        Object[] other_arr = new Object[3];

        arr[0] = inner_static;
        other_arr[1] = inner_static2;
        arr[2] = inner_static3;

        // Return them so that LSE doesn't delete them
        Object[][] array_of_arrays = {arr, other_arr};
        return array_of_arrays;
    }

    private static void $noinline$emptyMethod() {}

    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsBlocked(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: InstanceFieldSet field_name:Main.inner field_type:Reference write_barrier_kind:EmitWithNullCheck
    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
    /// CHECK: InstanceFieldSet field_name:Main.inner2 field_type:Reference write_barrier_kind:EmitWithNullCheck
    /// CHECK: MonitorOperation kind:enter
    /// CHECK: InstanceFieldSet field_name:Main.inner3 field_type:Reference write_barrier_kind:EmitWithNullCheck

    /// CHECK-START: Main Main.$noinline$testInstanceFieldSetsBlocked(Main, java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static Main $noinline$testInstanceFieldSetsBlocked(
            Main m, Object o, Object o2, Object o3) {
        m.inner = o;
        $noinline$emptyMethod();
        m.inner2 = o2;
        synchronized (m) {
            m.inner3 = o3;
        }
        return m;
    }

    /// CHECK-START: void Main.$noinline$testStaticFieldSetsBlocked(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: StaticFieldSet field_name:Main.inner_static field_type:Reference write_barrier_kind:EmitWithNullCheck
    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
    /// CHECK: StaticFieldSet field_name:Main.inner_static2 field_type:Reference write_barrier_kind:EmitWithNullCheck
    /// CHECK: MonitorOperation kind:enter
    /// CHECK: StaticFieldSet field_name:Main.inner_static3 field_type:Reference write_barrier_kind:EmitWithNullCheck

    /// CHECK-START: void Main.$noinline$testStaticFieldSetsBlocked(java.lang.Object, java.lang.Object, java.lang.Object) disassembly (after)
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static void $noinline$testStaticFieldSetsBlocked(Object o, Object o2, Object o3) {
        inner_static = o;
        $noinline$emptyMethod();
        inner_static2 = o2;
        Main m = new Main();
        synchronized (m) {
            inner_static3 = o3;
        }
    }

    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTIBlocked() disassembly (after)
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
    /// CHECK: InvokeStaticOrDirect method_name:Main.$noinline$emptyMethod
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck
    /// CHECK: MonitorOperation kind:enter
    /// CHECK: ArraySet needs_type_check:false can_trigger_gc:false write_barrier_kind:EmitNoNullCheck

    /// CHECK-START: java.lang.Object[] Main.$noinline$testArraySetsSameRTIBlocked() disassembly (after)
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK: ; card_table
    /// CHECK-NOT: ; card_table
    private static java.lang.Object[] $noinline$testArraySetsSameRTIBlocked() {
        Object[] arr = new Object[3];
        arr[0] = inner_static;
        $noinline$emptyMethod();
        arr[1] = inner_static2;
        Main m = new Main();
        synchronized (m) {
            arr[2] = inner_static3;
        }
        return arr;
    }

    Object inner;
    Object inner2;
    Object inner3;

    MultipleObject mo;
    MultipleObject mo2;

    static Object inner_static;
    static Object inner_static2;
    static Object inner_static3;
}
