// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Test line comment
/* Test mulitline
   comment
*/

namespace test {
macro ElementsKindTestHelper1(kind: constexpr ElementsKind): bool {
  if constexpr (
      (kind == ElementsKind::UINT8_ELEMENTS) ||
      (kind == ElementsKind::UINT16_ELEMENTS)) {
    return true;
  } else {
    return false;
  }
}

macro ElementsKindTestHelper2(kind: constexpr ElementsKind): constexpr bool {
  return (
      (kind == ElementsKind::UINT8_ELEMENTS) ||
      (kind == ElementsKind::UINT16_ELEMENTS));
}

macro LabelTestHelper1(): never
    labels Label1 {
  goto Label1;
}

macro LabelTestHelper2(): never
    labels Label2(Smi) {
  goto Label2(42);
}

macro LabelTestHelper3(): never
    labels Label3(Oddball, Smi) {
  goto Label3(Null, 7);
}

@export
macro TestConstexpr1() {
  check(FromConstexpr<bool>(
      IsFastElementsKind(ElementsKind::PACKED_SMI_ELEMENTS)));
}

@export
macro TestConstexprIf() {
  check(ElementsKindTestHelper1(ElementsKind::UINT8_ELEMENTS));
  check(ElementsKindTestHelper1(ElementsKind::UINT16_ELEMENTS));
  check(!ElementsKindTestHelper1(ElementsKind::UINT32_ELEMENTS));
}

@export
macro TestConstexprReturn() {
  check(FromConstexpr<bool>(
      ElementsKindTestHelper2(ElementsKind::UINT8_ELEMENTS)));
  check(FromConstexpr<bool>(
      ElementsKindTestHelper2(ElementsKind::UINT16_ELEMENTS)));
  check(!FromConstexpr<bool>(
      ElementsKindTestHelper2(ElementsKind::UINT32_ELEMENTS)));
  check(FromConstexpr<bool>(
      !ElementsKindTestHelper2(ElementsKind::UINT32_ELEMENTS)));
}

@export
macro TestGotoLabel(): Boolean {
  try {
    LabelTestHelper1() otherwise Label1;
  } label Label1 {
    return True;
  }
}

@export
macro TestGotoLabelWithOneParameter(): Boolean {
  try {
    LabelTestHelper2() otherwise Label2;
  } label Label2(smi: Smi) {
    check(smi == 42);
    return True;
  }
}

@export
macro TestGotoLabelWithTwoParameters(): Boolean {
  try {
    LabelTestHelper3() otherwise Label3;
  } label Label3(o: Oddball, smi: Smi) {
    check(o == Null);
    check(smi == 7);
    return True;
  }
}

builtin GenericBuiltinTest<T: type>(_param: T): JSAny {
  return Null;
}

GenericBuiltinTest<JSAny>(param: JSAny): JSAny {
  return param;
}

@export
macro TestBuiltinSpecialization() {
  check(GenericBuiltinTest<Smi>(0) == Null);
  check(GenericBuiltinTest<Smi>(1) == Null);
  check(GenericBuiltinTest<JSAny>(Undefined) == Undefined);
  check(GenericBuiltinTest<JSAny>(Undefined) == Undefined);
}

macro LabelTestHelper4(flag: constexpr bool): never
    labels Label4, Label5 {
  if constexpr (flag) {
    goto Label4;
  } else {
    goto Label5;
  }
}

macro CallLabelTestHelper4(flag: constexpr bool): bool {
  try {
    LabelTestHelper4(flag) otherwise Label4, Label5;
  } label Label4 {
    return true;
  } label Label5 {
    return false;
  }
}

@export
macro TestPartiallyUnusedLabel(): Boolean {
  const r1: bool = CallLabelTestHelper4(true);
  const r2: bool = CallLabelTestHelper4(false);

  if (r1 && !r2) {
    return True;
  } else {
    return False;
  }
}

macro GenericMacroTest<T: type>(_param: T): Object {
  return Undefined;
}

GenericMacroTest<Object>(param2: Object): Object {
  return param2;
}

macro GenericMacroTestWithLabels<T: type>(_param: T): Object
labels _X {
  return Undefined;
}

GenericMacroTestWithLabels<Object>(param2: Object): Object
    labels Y {
  return Cast<Smi>(param2) otherwise Y;
}

@export
macro TestMacroSpecialization() {
  try {
    const _smi0: Smi = 0;
    check(GenericMacroTest<Smi>(0) == Undefined);
    check(GenericMacroTest<Smi>(1) == Undefined);
    check(GenericMacroTest<Object>(Null) == Null);
    check(GenericMacroTest<Object>(False) == False);
    check(GenericMacroTest<Object>(True) == True);
    check((GenericMacroTestWithLabels<Smi>(0) otherwise Fail) == Undefined);
    check((GenericMacroTestWithLabels<Smi>(0) otherwise Fail) == Undefined);
    try {
      GenericMacroTestWithLabels<Object>(False) otherwise Expected;
    } label Expected {}
  } label Fail {
    unreachable;
  }
}

builtin TestHelperPlus1(x: Smi): Smi {
  return x + 1;
}
builtin TestHelperPlus2(x: Smi): Smi {
  return x + 2;
}

@export
macro TestFunctionPointers(implicit context: Context)(): Boolean {
  let fptr: builtin(Smi) => Smi = TestHelperPlus1;
  check(fptr(42) == 43);
  fptr = TestHelperPlus2;
  check(fptr(42) == 44);
  return True;
}

@export
macro TestVariableRedeclaration(implicit context: Context)(): Boolean {
  let _var1: int31 = FromConstexpr<bool>(42 == 0) ? 0 : 1;
  let _var2: int31 = FromConstexpr<bool>(42 == 0) ? 1 : 0;
  return True;
}

@export
macro TestTernaryOperator(x: Smi): Smi {
  const b: bool = x < 0 ? true : false;
  return b ? x - 10 : x + 100;
}

@export
macro TestFunctionPointerToGeneric() {
  const fptr1: builtin(Smi) => JSAny = GenericBuiltinTest<Smi>;
  const fptr2: builtin(JSAny) => JSAny = GenericBuiltinTest<JSAny>;

  check(fptr1(0) == Null);
  check(fptr1(1) == Null);
  check(fptr2(Undefined) == Undefined);
  check(fptr2(Undefined) == Undefined);
}

type ObjectToObject = builtin(Context, JSAny) => JSAny;
@export
macro TestTypeAlias(x: ObjectToObject): BuiltinPtr {
  return x;
}

@export
macro TestUnsafeCast(implicit context: Context)(n: Number): Boolean {
  if (TaggedIsSmi(n)) {
    const m: Smi = UnsafeCast<Smi>(n);

    check(TestHelperPlus1(m) == 11);
    return True;
  }
  return False;
}

@export
macro TestHexLiteral() {
  check(Convert<intptr>(0xffff) + 1 == 0x10000);
  check(Convert<intptr>(-0xffff) == -65535);
}

@export
macro TestLargeIntegerLiterals(implicit c: Context)() {
  let _x: int32 = 0x40000000;
  let _y: int32 = 0x7fffffff;
}

@export
macro TestMultilineAssert() {
  const someVeryLongVariableNameThatWillCauseLineBreaks: Smi = 5;
  check(
      someVeryLongVariableNameThatWillCauseLineBreaks > 0 &&
      someVeryLongVariableNameThatWillCauseLineBreaks < 10);
}

@export
macro TestNewlineInString() {
  Print('Hello, World!\n');
}

const kConstexprConst: constexpr int31 = 5;
const kIntptrConst: intptr = 4;
const kSmiConst: Smi = 3;

@export
macro TestModuleConstBindings() {
  check(kConstexprConst == Int32Constant(5));
  check(kIntptrConst == 4);
  check(kSmiConst == 3);
}

@export
macro TestLocalConstBindings() {
  const x: constexpr int31 = 3;
  const xSmi: Smi = x;
  {
    const x: Smi = x + FromConstexpr<Smi>(1);
    check(x == xSmi + 1);
    const xSmi: Smi = x;
    check(x == xSmi);
    check(x == 4);
  }
  check(xSmi == 3);
  check(x == xSmi);
}

struct TestStructA {
  indexes: FixedArray;
  i: Smi;
  k: Number;
}

struct TestStructB {
  x: TestStructA;
  y: Smi;
}

@export
macro TestStruct1(i: TestStructA): Smi {
  return i.i;
}

@export
macro TestStruct2(implicit context: Context)(): TestStructA {
  return TestStructA{
    indexes: UnsafeCast<FixedArray>(kEmptyFixedArray),
    i: 27,
    k: 31
  };
}

@export
macro TestStruct3(implicit context: Context)(): TestStructA {
  let a: TestStructA =
  TestStructA{indexes: UnsafeCast<FixedArray>(kEmptyFixedArray), i: 13, k: 5};
  let _b: TestStructA = a;
  const c: TestStructA = TestStruct2();
  a.i = TestStruct1(c);
  a.k = a.i;
  let d: TestStructB;
  d.x = a;
  d = TestStructB{x: a, y: 7};
  let _e: TestStructA = d.x;
  let f: Smi = TestStructA{
    indexes: UnsafeCast<FixedArray>(kEmptyFixedArray),
    i: 27,
    k: 31
  }.i;
  f = TestStruct2().i;
  return a;
}

struct TestStructC {
  x: TestStructA;
  y: TestStructA;
}

@export
macro TestStruct4(implicit context: Context)(): TestStructC {
  return TestStructC{x: TestStruct2(), y: TestStruct2()};
}

macro TestStructInLabel(implicit context: Context)(): never labels
Foo(TestStructA) {
  goto Foo(TestStruct2());
}
@export  // Silence unused warning.
macro CallTestStructInLabel(implicit context: Context)() {
  try {
    TestStructInLabel() otherwise Foo;
  } label Foo(_s: TestStructA) {}
}

// This macro tests different versions of the for-loop where some parts
// are (not) present.
@export
macro TestForLoop() {
  let sum: Smi = 0;
  for (let i: Smi = 0; i < 5; ++i) sum += i;
  check(sum == 10);

  sum = 0;
  let j: Smi = 0;
  for (; j < 5; ++j) sum += j;
  check(sum == 10);

  sum = 0;
  j = 0;
  for (; j < 5;) sum += j++;
  check(sum == 10);

  // Check that break works. No test expression.
  sum = 0;
  for (let i: Smi = 0;; ++i) {
    if (i == 5) break;
    sum += i;
  }
  check(sum == 10);

  sum = 0;
  j = 0;
  for (;;) {
    if (j == 5) break;
    sum += j;
    j++;
  }
  check(sum == 10);

  // The following tests are the same as above, but use continue to skip
  // index 3.
  sum = 0;
  for (let i: Smi = 0; i < 5; ++i) {
    if (i == 3) continue;
    sum += i;
  }
  check(sum == 7);

  sum = 0;
  j = 0;
  for (; j < 5; ++j) {
    if (j == 3) continue;
    sum += j;
  }
  check(sum == 7);

  sum = 0;
  j = 0;
  for (; j < 5;) {
    if (j == 3) {
      j++;
      continue;
    }
    sum += j;
    j++;
  }
  check(sum == 7);

  sum = 0;
  for (let i: Smi = 0;; ++i) {
    if (i == 3) continue;
    if (i == 5) break;
    sum += i;
  }
  check(sum == 7);

  sum = 0;
  j = 0;
  for (;;) {
    if (j == 3) {
      j++;
      continue;
    }

    if (j == 5) break;
    sum += j;
    j++;
  }
  check(sum == 7);

  j = 0;
  try {
    for (;;) {
      if (++j == 10) goto Exit;
    }
  } label Exit {
    check(j == 10);
  }

  // Test if we can handle uninitialized values on the stack.
  let _i: Smi;
  for (let j: Smi = 0; j < 10; ++j) {
  }
}

@export
macro TestSubtyping(x: Smi) {
  const _foo: JSAny = x;
}

macro IncrementIfSmi<A: type>(x: A): A {
  typeswitch (x) {
    case (x: Smi): {
      return x + 1;
    }
    case (o: A): {
      return o;
    }
  }
}

type NumberOrFixedArray = Number|FixedArray;
macro TypeswitchExample(implicit context: Context)(x: NumberOrFixedArray):
    int32 {
  let result: int32 = 0;
  typeswitch (IncrementIfSmi(x)) {
    case (_x: FixedArray): {
      result = result + 1;
    }
    case (Number): {
      result = result + 2;
    }
  }

  result = result * 10;

  typeswitch (IncrementIfSmi(x)) {
    case (x: Smi): {
      result = result + Convert<int32>(x);
    }
    case (a: FixedArray): {
      result = result + Convert<int32>(a.length);
    }
    case (_x: HeapNumber): {
      result = result + 7;
    }
  }

  return result;
}

@export
macro TestTypeswitch(implicit context: Context)() {
  check(TypeswitchExample(FromConstexpr<Smi>(5)) == 26);
  const a: FixedArray = AllocateZeroedFixedArray(3);
  check(TypeswitchExample(a) == 13);
  check(TypeswitchExample(FromConstexpr<Number>(0.5)) == 27);
}

@export
macro TestTypeswitchAsanLsanFailure(implicit context: Context)(obj: Object) {
  typeswitch (obj) {
    case (_o: Smi): {
    }
    case (_o: JSTypedArray): {
    }
    case (_o: JSReceiver): {
    }
    case (_o: HeapObject): {
    }
  }
}

macro ExampleGenericOverload<A: type>(o: Object): A {
  return o;
}
macro ExampleGenericOverload<A: type>(o: Smi): A {
  return o + 1;
}

@export
macro TestGenericOverload(implicit context: Context)() {
  const xSmi: Smi = 5;
  const xObject: Object = xSmi;
  check(ExampleGenericOverload<Smi>(xSmi) == 6);
  check(UnsafeCast<Smi>(ExampleGenericOverload<Object>(xObject)) == 5);
}

@export
macro TestEquality(implicit context: Context)() {
  const notEqual: bool =
      AllocateHeapNumberWithValue(0.5) != AllocateHeapNumberWithValue(0.5);
  check(!notEqual);
  const equal: bool =
      AllocateHeapNumberWithValue(0.5) == AllocateHeapNumberWithValue(0.5);
  check(equal);
}

@export
macro TestOrAnd(x: bool, y: bool, z: bool): bool {
  return x || y && z ? true : false;
}

@export
macro TestAndOr(x: bool, y: bool, z: bool): bool {
  return x && y || z ? true : false;
}

@export
macro TestLogicalOperators() {
  check(TestAndOr(true, true, true));
  check(TestAndOr(true, true, false));
  check(TestAndOr(true, false, true));
  check(!TestAndOr(true, false, false));
  check(TestAndOr(false, true, true));
  check(!TestAndOr(false, true, false));
  check(TestAndOr(false, false, true));
  check(!TestAndOr(false, false, false));
  check(TestOrAnd(true, true, true));
  check(TestOrAnd(true, true, false));
  check(TestOrAnd(true, false, true));
  check(TestOrAnd(true, false, false));
  check(TestOrAnd(false, true, true));
  check(!TestOrAnd(false, true, false));
  check(!TestOrAnd(false, false, true));
  check(!TestOrAnd(false, false, false));
}

@export
macro TestCall(i: Smi): Smi labels A {
  if (i < 5) return i;
  goto A;
}

@export
macro TestOtherwiseWithCode1() {
  let v: Smi = 0;
  let s: Smi = 1;
  try {
    TestCall(10) otherwise goto B(++s);
  } label B(v1: Smi) {
    v = v1;
  }
  assert(v == 2);
}

@export
macro TestOtherwiseWithCode2() {
  let s: Smi = 0;
  for (let i: Smi = 0; i < 10; ++i) {
    TestCall(i) otherwise break;
    ++s;
  }
  assert(s == 5);
}

@export
macro TestOtherwiseWithCode3() {
  let s: Smi = 0;
  for (let i: Smi = 0; i < 10; ++i) {
    s += TestCall(i) otherwise break;
  }
  assert(s == 10);
}

@export
macro TestForwardLabel() {
  try {
    goto A;
  } label A {
    goto B(5);
  } label B(b: Smi) {
    assert(b == 5);
  }
}

@export
macro TestQualifiedAccess(implicit context: Context)() {
  const s: Smi = 0;
  check(!Is<JSArray>(s));
}

@export
macro TestCatch1(implicit context: Context)(): Smi {
  let r: Smi = 0;
  try {
    ThrowTypeError(MessageTemplate::kInvalidArrayLength);
  } catch (_e) {
    r = 1;
    return r;
  }
}

@export
macro TestCatch2Wrapper(implicit context: Context)(): never {
  ThrowTypeError(MessageTemplate::kInvalidArrayLength);
}

@export
macro TestCatch2(implicit context: Context)(): Smi {
  let r: Smi = 0;
  try {
    TestCatch2Wrapper();
  } catch (_e) {
    r = 2;
    return r;
  }
}

@export
macro TestCatch3WrapperWithLabel(implicit context: Context)():
    never labels _Abort {
  ThrowTypeError(MessageTemplate::kInvalidArrayLength);
}

@export
macro TestCatch3(implicit context: Context)(): Smi {
  let r: Smi = 0;
  try {
    TestCatch3WrapperWithLabel() otherwise Abort;
  } catch (_e) {
    r = 2;
    return r;
  } label Abort {
    return -1;
  }
}

// This test doesn't actually test the functionality of iterators,
// it's only purpose is to make sure tha the CSA macros in the
// IteratorBuiltinsAssembler match the signatures provided in
// iterator.tq.
@export
transitioning macro TestIterator(implicit context: Context)(
    o: JSReceiver, map: Map) {
  try {
    const t1: JSAny = iterator::GetIteratorMethod(o);
    const t2: iterator::IteratorRecord = iterator::GetIterator(o);

    const _t3: JSAny = iterator::IteratorStep(t2) otherwise Fail;
    const _t4: JSAny = iterator::IteratorStep(t2, map) otherwise Fail;

    const _t5: JSAny = iterator::IteratorValue(o);
    const _t6: JSAny = iterator::IteratorValue(o, map);

    const _t7: JSArray = iterator::IterableToList(t1, t1);

    iterator::IteratorCloseOnException(t2);
  } label Fail {}
}

@export
macro TestFrame1(implicit context: Context)() {
  const f: Frame = LoadFramePointer();
  const frameType: FrameType =
      Cast<FrameType>(f.context_or_frame_type) otherwise unreachable;
  assert(frameType == STUB_FRAME);
  assert(f.caller == LoadParentFramePointer());
  typeswitch (f) {
    case (_f: StandardFrame): {
      unreachable;
    }
    case (_f: ArgumentsAdaptorFrame): {
      unreachable;
    }
    case (_f: StubFrame): {
    }
  }
}

@export
macro TestNew(implicit context: Context)() {
  const f: JSArray = NewJSArray();
  check(f.IsEmpty());
  f.length = 0;
}

struct TestInner {
  macro SetX(newValue: int32) {
    this.x = newValue;
  }
  macro GetX(): int32 {
    return this.x;
  }
  x: int32;
  y: int32;
}

struct TestOuter {
  a: int32;
  b: TestInner;
  c: int32;
}

@export
macro TestStructConstructor(implicit context: Context)() {
  // Test default constructor
  let a: TestOuter = TestOuter{a: 5, b: TestInner{x: 6, y: 7}, c: 8};
  check(a.a == 5);
  check(a.b.x == 6);
  check(a.b.y == 7);
  check(a.c == 8);
  a.b.x = 1;
  check(a.b.x == 1);
  a.b.SetX(2);
  check(a.b.x == 2);
  check(a.b.GetX() == 2);
}

class InternalClass extends HeapObject {
  macro Flip() labels NotASmi {
    const tmp = Cast<Smi>(this.b) otherwise NotASmi;
    this.b = this.a;
    this.a = tmp;
  }
  a: Smi;
  b: Number;
}

macro NewInternalClass(x: Smi): InternalClass {
  return new InternalClass{a: x, b: x + 1};
}

@export
macro TestInternalClass(implicit context: Context)() {
  const o = NewInternalClass(5);
  o.Flip() otherwise unreachable;
  check(o.a == 6);
  check(o.b == 5);
}

struct StructWithConst {
  macro TestMethod1(): int32 {
    return this.b;
  }
  macro TestMethod2(): Object {
    return this.a;
  }
  a: Object;
  const b: int32;
}

@export
macro TestConstInStructs() {
  const x = StructWithConst{a: Null, b: 1};
  let y = StructWithConst{a: Null, b: 1};
  y.a = Undefined;
  const _copy = x;

  check(x.TestMethod1() == 1);
  check(x.TestMethod2() == Null);
}

@export
macro TestParentFrameArguments(implicit context: Context)() {
  const parentFrame = LoadParentFramePointer();
  const castFrame = Cast<StandardFrame>(parentFrame) otherwise unreachable;
  const arguments = GetFrameArguments(castFrame, 1);
  ArgumentsIterator{arguments, current: 0};
}

struct TestIterator {
  macro Next(): Object labels NoMore {
    if (this.count-- == 0) goto NoMore;
    return TheHole;
  }
  count: Smi;
}

@export
macro TestNewFixedArrayFromSpread(implicit context: Context)(): Object {
  let i = TestIterator{count: 5};
  return new FixedArray{map: kFixedArrayMap, length: 5, objects: ...i};
}

class SmiPair extends HeapObject {
  macro GetA():&Smi {
    return & this.a;
  }
  a: Smi;
  b: Smi;
}

macro Swap<T: type>(a:&T, b:&T) {
  const tmp = * a;
  * a = * b;
  * b = tmp;
}

@export
macro TestReferences() {
  const array = new SmiPair{a: 7, b: 2};
  const ref:&Smi = & array.a;
  * ref = 3 + * ref;
  -- * ref;
  Swap(& array.b, array.GetA());
  check(array.a == 2);
  check(array.b == 9);
}

@export
macro TestSlices() {
  const it = TestIterator{count: 3};
  const a = new FixedArray{map: kFixedArrayMap, length: 3, objects: ...it};
  check(a.length == 3);

  const oneTwoThree = Convert<Smi>(123);
  a.objects[0] = oneTwoThree;
  const firstRef:&Object = & a.objects[0];
  check(TaggedEqual(* firstRef, oneTwoThree));

  const slice: torque_internal::Slice<Object> = & a.objects;
  const firstRefAgain:&Object = slice.TryAtIndex(0) otherwise unreachable;
  check(TaggedEqual(* firstRefAgain, oneTwoThree));

  const threeTwoOne = Convert<Smi>(321);
  * firstRefAgain = threeTwoOne;
  check(TaggedEqual(a.objects[0], threeTwoOne));

  // *slice;             // error, not allowed
  // a.objects;          // error, not allowed
  // a.objects = slice;  // error, not allowed

  // TODO(gsps): Currently errors, but should be allowed:
  // const _sameSlice: torque_internal::Slice<Object> = &(*slice);
  // (*slice)[0] : Smi
}

@export
macro TestSliceEnumeration(implicit context: Context)(): Undefined {
  const fixedArray: FixedArray = AllocateZeroedFixedArray(3);
  for (let i: intptr = 0; i < 3; i++) {
    check(UnsafeCast<Smi>(fixedArray.objects[i]) == 0);
    fixedArray.objects[i] = Convert<Smi>(i) + 3;
  }

  let slice = & fixedArray.objects;
  for (let i: intptr = 0; i < slice.length; i++) {
    let ref = slice.TryAtIndex(i) otherwise unreachable;
    const value = UnsafeCast<Smi>(* ref);
    check(value == Convert<Smi>(i) + 3);
    * ref = value + 4;
  }

  let it = slice.Iterator();
  let count: Smi = 0;
  while (true) {
    const value = UnsafeCast<Smi>(it.Next() otherwise break);
    check(value == count + 7);
    count++;
  }
  check(count == 3);
  check(it.Empty());

  return Undefined;
}

@export
macro TestStaticAssert() {
  StaticAssert(1 + 2 == 3);
}

class SmiBox extends HeapObject {
  value: Smi;
  unrelated: Smi;
}

builtin NewSmiBox(implicit context: Context)(value: Smi): SmiBox {
  return new SmiBox{value, unrelated: 0};
}

@export
macro TestLoadEliminationFixed(implicit context: Context)() {
  const box = NewSmiBox(123);
  const v1 = box.value;
  box.unrelated = 999;
  const v2 = (box.unrelated == 0) ? box.value : box.value;
  StaticAssert(TaggedEqual(v1, v2));

  box.value = 11;
  const v3 = box.value;
  const eleven: Smi = 11;
  StaticAssert(TaggedEqual(v3, eleven));
}

@export
macro TestLoadEliminationVariable(implicit context: Context)() {
  const a = UnsafeCast<FixedArray>(kEmptyFixedArray);
  const box = NewSmiBox(1);
  const v1 = a.objects[box.value];
  const u1 = a.objects[box.value + 2];
  const v2 = a.objects[box.value];
  const u2 = a.objects[box.value + 2];
  StaticAssert(TaggedEqual(v1, v2));
  StaticAssert(TaggedEqual(u1, u2));
}

@export
macro TestRedundantArrayElementCheck(implicit context: Context)(): Smi {
  const a = kEmptyFixedArray;
  for (let i: Smi = 0; i < a.length; i++) {
    if (a.objects[i] == TheHole) {
      if (a.objects[i] == TheHole) {
        return -1;
      } else {
        StaticAssert(false);
      }
    }
  }
  return 1;
}

@export
macro TestRedundantSmiCheck(implicit context: Context)(): Smi {
  const a = kEmptyFixedArray;
  const x = a.objects[1];
  typeswitch (x) {
    case (Smi): {
      Cast<Smi>(x) otherwise VerifiedUnreachable();
      return -1;
    }
    case (Object): {
    }
  }
  return 1;
}

struct SBox<T: type> {
  value: T;
}

@export
macro TestGenericStruct1(): intptr {
  const i: intptr = 123;
  let box = SBox{value: i};
  let boxbox: SBox<SBox<intptr>> = SBox{value: box};
  check(box.value == 123);
  boxbox.value.value *= 2;
  check(boxbox.value.value == 246);
  return boxbox.value.value;
}

struct TestTuple<T1: type, T2: type> {
  const fst: T1;
  const snd: T2;
}

macro TupleSwap<T1: type, T2: type>(tuple: TestTuple<T1, T2>):
    TestTuple<T2, T1> {
  return TestTuple{fst: tuple.snd, snd: tuple.fst};
}

@export
macro TestGenericStruct2():
    TestTuple<TestTuple<intptr, Smi>, TestTuple<Smi, intptr>> {
  const intptrAndSmi = TestTuple<intptr, Smi>{fst: 1, snd: 2};
  const smiAndIntptr = TupleSwap(intptrAndSmi);
  check(intptrAndSmi.fst == smiAndIntptr.snd);
  check(intptrAndSmi.snd == smiAndIntptr.fst);
  const tupleTuple =
      TestTuple<TestTuple<intptr, Smi>>{fst: intptrAndSmi, snd: smiAndIntptr};
  return tupleTuple;
}

macro BranchAndWriteResult(x: Smi, box: SmiBox): bool {
  if (x > 5 || x < 0) {
    box.value = 1;
    return true;
  } else {
    box.value = 2;
    return false;
  }
}

@export
macro TestBranchOnBoolOptimization(implicit context: Context)(input: Smi) {
  const box = NewSmiBox(1);
  // If the two branches get combined into one, we should be able to determine
  // the value of {box} statically.
  if (BranchAndWriteResult(input, box)) {
    StaticAssert(box.value == 1);
  } else {
    StaticAssert(box.value == 2);
  }
}

bitfield struct TestBitFieldStruct extends uint8 {
  a: bool: 1 bit;
  b: uint16: 3 bit;
  c: uint32: 3 bit;
  d: bool: 1 bit;
}

@export
macro TestBitFieldLoad(
    val: TestBitFieldStruct, expectedA: bool, expectedB: uint16,
    expectedC: uint32, expectedD: bool) {
  check(val.a == expectedA);
  check(val.b == expectedB);
  check(val.c == expectedC);
  check(val.d == expectedD);
}

@export
macro TestBitFieldStore(val: TestBitFieldStruct) {
  let val: TestBitFieldStruct = val;  // Get a mutable local copy.
  const a: bool = val.a;
  const b: uint16 = val.b;
  let c: uint32 = val.c;
  const d: bool = val.d;

  val.a = !a;
  TestBitFieldLoad(val, !a, b, c, d);

  c = Unsigned(7 - Signed(val.c));
  val.c = c;
  TestBitFieldLoad(val, !a, b, c, d);

  val.d = val.b == val.c;
  TestBitFieldLoad(val, !a, b, c, b == c);
}

@export
macro TestBitFieldInit(a: bool, b: uint16, c: uint32, d: bool) {
  const val: TestBitFieldStruct = TestBitFieldStruct{a: a, b: b, c: c, d: d};
  TestBitFieldLoad(val, a, b, c, d);
}

// Some other bitfield structs, to verify getting uintptr values out of word32
// structs and vice versa.
bitfield struct TestBitFieldStruct2 extends uint32 {
  a: uintptr: 5 bit;
  b: uintptr: 6 bit;
}
bitfield struct TestBitFieldStruct3 extends uintptr {
  c: bool: 1 bit;
  d: uint32: 9 bit;
  e: uintptr: 17 bit;
}

@export
macro TestBitFieldUintptrOps(
    val2: TestBitFieldStruct2, val3: TestBitFieldStruct3) {
  let val2: TestBitFieldStruct2 = val2;  // Get a mutable local copy.
  let val3: TestBitFieldStruct3 = val3;  // Get a mutable local copy.

  // Caller is expected to provide these exact values, so we can verify
  // reading values before starting to write anything.
  check(val2.a == 3);
  check(val2.b == 61);
  check(val3.c);
  check(val3.d == 500);
  check(val3.e == 0x1cc);

  val2.b = 16;
  check(val2.a == 3);
  check(val2.b == 16);

  val2.b++;
  check(val2.a == 3);
  check(val2.b == 17);

  val3.d = 99;
  val3.e = 1234;
  check(val3.c);
  check(val3.d == 99);
  check(val3.e == 1234);
}

@export
class ExportedSubClass extends ExportedSubClassBase {
  c_field: int32;
  d_field: int32;
  e_field: Smi;
}

@export
class ExportedSubClassBase extends HeapObject {
  a: HeapObject;
  b: HeapObject;
}

@abstract
class AbstractInternalClass extends HeapObject {
}

class AbstractInternalClassSubclass1 extends AbstractInternalClass {}

class AbstractInternalClassSubclass2 extends AbstractInternalClass {}

class InternalClassWithSmiElements extends FixedArrayBase {
  data: Smi;
  object: Oddball;
  entries[length]: Smi;
}

struct InternalClassStructElement {
  a: Smi;
  b: Smi;
}

class InternalClassWithStructElements extends HeapObject {
  dummy1: int32;
  dummy2: int32;
  const count: Smi;
  data: Smi;
  object: Object;
  entries[count]: Smi;
  more_entries[count]: InternalClassStructElement;
}

struct SmiGeneratorIterator {
  macro Next(): Smi labels _NoMore {
    return this.value++;
  }
  value: Smi;
}

struct InternalClassStructElementGeneratorIterator {
  macro Next(): InternalClassStructElement labels _NoMore {
    return InternalClassStructElement{a: this.value++, b: this.value++};
  }
  value: Smi;
}

@export
macro TestFullyGeneratedClassWithElements() {
  // Test creation, initialization and access of a fully generated class with
  // simple (Smi) elements
  const length: Smi = Convert<Smi>(3);
  const object1 = new InternalClassWithSmiElements{
    length,
    data: 0,
    object: Undefined,
    entries: ...SmiGeneratorIterator {
      value: 11
    }
  };
  assert(object1.length == 3);
  assert(object1.data == 0);
  assert(object1.object == Undefined);
  assert(object1.entries[0] == 11);
  assert(object1.entries[1] == 12);
  assert(object1.entries[2] == 13);

  // Test creation, initialization and access of a fully generated class
  // with elements that are a struct.
  const object2 = new InternalClassWithStructElements{
    dummy1: 44,
    dummy2: 45,
    count: length,
    data: 55,
    object: Undefined,
    entries: ...SmiGeneratorIterator{value: 3},
    more_entries: ...InternalClassStructElementGeneratorIterator {
      value: 1
    }
  };

  assert(object2.dummy1 == 44);
  assert(object2.dummy2 == 45);
  assert(object2.count == 3);
  assert(object2.data == 55);
  assert(object2.object == Undefined);
  assert(object2.entries[0] == 3);
  assert(object2.entries[1] == 4);
  assert(object2.entries[2] == 5);
  assert(object2.more_entries[0].a == 1);
  assert(object2.more_entries[0].b == 2);
  assert(object2.more_entries[1].a == 3);
  assert(object2.more_entries[1].b == 4);
  assert(object2.more_entries[2].a == 5);
  assert(object2.more_entries[2].b == 6);
}

@export
macro TestFullyGeneratedClassFromCpp(): ExportedSubClass {
  return new
  ExportedSubClass{a: Null, b: Null, c_field: 7, d_field: 8, e_field: 9};
}
}