// 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>(_c: Context, _param: T): JSAny {
    return Null;
  }

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

  @export
  macro TestBuiltinSpecialization(c: Context) {
    check(GenericBuiltinTest<Smi>(c, 0) == Null);
    check(GenericBuiltinTest<Smi>(c, 1) == Null);
    check(GenericBuiltinTest<JSAny>(c, Undefined) == Undefined);
    check(GenericBuiltinTest<JSAny>(c, 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(_context: Context, x: Smi): Smi {
    return x + 1;
  }
  builtin TestHelperPlus2(_context: Context, x: Smi): Smi {
    return x + 2;
  }

  @export
  macro TestFunctionPointers(implicit context: Context)(): Boolean {
    let fptr: builtin(Context, Smi) => Smi = TestHelperPlus1;
    check(fptr(context, 42) == 43);
    fptr = TestHelperPlus2;
    check(fptr(context, 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(c: Context) {
    const fptr1: builtin(Context, Smi) => JSAny = GenericBuiltinTest<Smi>;
    const fptr2: builtin(Context, JSAny) => JSAny = GenericBuiltinTest<JSAny>;

    check(fptr1(c, 0) == Null);
    check(fptr1(c, 1) == Null);
    check(fptr2(c, Undefined) == Undefined);
    check(fptr2(c, 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(context, 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;
    }
    label Abort {
      return -1;
    }
    catch (_e) {
      r = 2;
      return r;
    }
  }

  // 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
  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, t5);
    }
    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);
  }

  // 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;
  }

  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};
  }
}