/*
 * Copyright (C) 2019 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 Foo {
  volatile Object bar;
}

public class Main {

  public static void main(String[] args) {
    Main main = new Main();
    main.test();
    System.out.println("passed");
  }

  // Check that no explicit null check is emitted for the field load of volatile
  // field `Foo.bar` before entering the Baker read barrier thunk.
  //
  // Note: We cannot check the ARM64 assembly code of the Baker read barrier
  // thunk code, as it is not emitted in the CFG output.
  //
  /// CHECK-START-ARM64: void Main.test() disassembly (after)
  /// CHECK:       <<Foo:l\d+>> InstanceFieldGet [{{l\d+}}] field_name:Main.foo field_type:Reference loop:<<Loop:B\d+>>
  /// CHECK:       NullCheck [<<Foo>>] dex_pc:<<PC:\d+>> loop:<<Loop>>
  /// CHECK-NEXT:  InstanceFieldGet [<<Foo>>] dex_pc:<<PC>> field_name:Foo.bar field_type:Reference loop:<<Loop>>
  /// CHECK-NEXT:      add w<<BaseRegNum:\d+>>, {{w\d+}}, #0x8 (8)
  /// CHECK-NEXT:      adr lr, #+0x{{c|10}}
  // The following instruction (generated by
  // `art::arm64::CodeGeneratorARM64::EmitBakerReadBarrierCbnz`) checks the
  // Marking Register (X20) and goes into the Baker read barrier thunk if MR is
  // not null. The null offset (#+0x0) in the CBNZ instruction is a placeholder
  // for the offset to the Baker read barrier thunk (which is not yet set when
  // the CFG output is emitted).
  /// CHECK-NEXT:      cbnz x20, #+0x0
  /// CHECK-NEXT:      ldar {{w\d+}}, [x<<BaseRegNum>>]

  public void test() {
    // Continually check that reading field `foo.bar` throws a
    // NullPointerException while allocating over 64 MiB of memory (with heap
    // size limited to 16 MiB), in order to increase memory pressure and
    // eventually trigger a concurrent garbage collection, which will start by
    // putting the GC in marking mode and enable read barriers (when the
    // Concurrent Copying collector is used).
    for (int i = 0; i != 64 * 1024; ++i) {
      allocateAtLeast1KiB();
      try {
        // Read volatile field `bar` of `foo`, which is null, and is expected
        // to produce a NullPointerException. On ARM64, this is implemented as a
        // load-acquire (LDAR instruction).
        //
        // When the Concurrent Copying GC is marking, read barriers are enabled
        // and the field load executes code from a Baker read barrier thunk.
        // On ARM64, there used to be a bug in this thunk for the load-acquire
        // case, where an explicit null check was missing, triggering an
        // unhandled SIGSEGV when trying to load the lock word from the volatile
        // field (b/140507091).
        Object foo_bar = foo.bar;
      } catch (NullPointerException e) {
        continue;
      }
      // We should not be here.
      throw new Error("Expected NullPointerException");
    }
  }

  // Allocate at least 1 KiB of memory on the managed heap.
  // Retain some allocated memory and release old allocations so that the
  // garbage collector has something to do.
  public static void allocateAtLeast1KiB() {
    memory[allocationIndex] = new Object[1024 / 4];
    ++allocationIndex;
    if (allocationIndex == memory.length) {
      allocationIndex = 0;
    }
  }

  public static Object[] memory = new Object[1024];
  public static int allocationIndex = 0;

  private Foo foo;

}
