// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "asm.h"
#include "setjmp_impl.h"
#include <zircon/tls.h>

#define DW_CFA_expression       0x10
#define DW_CFA_val_expression   0x16
#define DW_OP_breg0             0x70
#define DW_OP_breg16            (DW_OP_breg0 + 16)
#define DW_OP_deref             0x06
#define DW_OP_xor               0x27

// CFI to find regno at [x16, #8*index].
.macro jb_cfi regno, index
    .if 8 * \index < 0x7f
    .cfi_escape DW_CFA_expression, \regno, 2, \
                DW_OP_breg16, 8 * \index
    .elseif (8 * \index) < 0x3fff
    .cfi_escape DW_CFA_expression, \regno, 3, \
                DW_OP_breg16, ((8 * \index) & 0x7f) | 0x80, (8 * \index) >> 7
    .else
    .error "offset too large for two-byte SLEB128"
    .endif
.endm

// CFI to compute regno as ([x0, #8*index] ^ [x16, #8*index]).
.macro jb_cfi_mangled regno, index
    .ifgt (8 * \index) - 0x7f
    .error "offset too large for one-byte LEB128"
    .endif
    .cfi_escape DW_CFA_val_expression, \regno, 7, \
                DW_OP_breg0, 8 * \index, DW_OP_deref, \
                DW_OP_breg16, 8 * \index, DW_OP_deref, DW_OP_xor
.endm

// CFI to compute regno as (mangled ^ mangler).
.macro jb_cfi_mangled_reg regno, mangled, mangler
    .cfi_escape DW_CFA_val_expression, \regno, 5, \
                DW_OP_breg0 + \mangled, 0, DW_OP_breg0 + \mangler, 0, DW_OP_xor
.endm

.macro restore_pair reg1, reg2, index
    ldp \reg1, \reg2, [x16, #8*\index]
    .cfi_same_value \reg1
    .cfi_same_value \reg2
.endm

ENTRY(longjmp)

    // The sanitizer runtime wants to be informed of non-local exits.
    // Call __asan_handle_no_return() before doing the actual longjmp.
#if __has_feature(address_sanitizer)
    // Save our incoming argument registers and return address on the stack
    // around calling __asan_handle_no_return.  We save the zero word (xzr)
    // just to keep the stack aligned correctly as required by the ABI.
    push_regs x0, x1
    push_regs x30, xzr
    bl __asan_handle_no_return
    pop_regs x30, xzr
    pop_regs x0, x1
#endif

    // Move the jmp_buf pointer to a temporary register.
    // We'll use x0 as a scratch register since we clobber it on return anyway.
    mov x16, x0
    .cfi_register x0, x16

    // Find the manglers.
    adrp x0, __setjmp_manglers
    add x0, x0, #:lo12:__setjmp_manglers

    // Load the words that need to be demangled into temporaries.
    // These just hold values that were in the jmp_buf, so we don't
    // care about leaking them.
.ifne JB_SP - JB_PC - 1
.error "JB_SP expected to follow JB_PC immediately"
.endif
    ldp x4, x5, [x16, #8*JB_PC]
.ifne JB_USP - JB_FP - 1
.error "JB_USP expected to follow JB_FP immediately"
.endif
    ldp x6, x7, [x16, #8*JB_FP]

    // The next instruction clobbers the state of longjmp's caller.
    // So from here on, we'll use CFI that unwinds to setjmp's caller instead.
    // Both callers have the same x0 value, which we now have in x16.
    .cfi_undefined x1
    jb_cfi 19, JB_X(19)
    jb_cfi 20, JB_X(20)
    jb_cfi 21, JB_X(21)
    jb_cfi 22, JB_X(22)
    jb_cfi 23, JB_X(23)
    jb_cfi 24, JB_X(24)
    jb_cfi 25, JB_X(25)
    jb_cfi 26, JB_X(26)
    jb_cfi 27, JB_X(27)
    jb_cfi_mangled 29, JB_FP
    jb_cfi_mangled 30, JB_PC
    jb_cfi_mangled 31, JB_SP
    //jb_cfi_mangled ?, JB_USP  -- No DWARF register number for it!
    jb_cfi 64+8, JB_D(8)
    jb_cfi 64+9, JB_D(9)
    jb_cfi 64+10, JB_D(10)
    jb_cfi 64+11, JB_D(11)
    jb_cfi 64+12, JB_D(12)
    jb_cfi 64+13, JB_D(13)
    jb_cfi 64+14, JB_D(14)

    // We don't want to leak the raw mangler values, so load
    // them into their target registers rather than temporaries
    // so we don't have more temporaries to clear.
    ldp x30, x3, [x0, #8*JB_PC]
    jb_cfi_mangled_reg 30, 4, 30        // x30 (LR) = x30 ^ x4
    jb_cfi_mangled_reg 31, 5, 3         // sp = x3 ^ x5
    ldp x29, x2, [x0, #8*JB_FP]
    jb_cfi_mangled_reg 29, 6, 29        // x29 (FP) = x29 ^ x6
    //jb_cfi_mangled_reg ?, 7, 2        // unsafe_sp = x2 ^ x7

    // Get the thread pointer, where the unsafe SP is stored.
    // Reuse x0 so the address of __setjmp_manglers is not leaked.
    mrs x0, TPIDR_EL0

    // Demangle in place.
    eor x30, x30, x4    // PC (LR)
    .cfi_same_value x30
    eor x3, x3, x5      // SP
    .cfi_register sp, x3
    eor x29, x29, x6    // FP
    .cfi_same_value x29
    eor x2, x2, x7      // Unsafe SP
    //.cfi_register unsafe_sp, x2  -- No DWARF register number for it!

    // Restore all the vanilla callee-saves registers.
    restore_pair x19, x20, JB_X(19)
    restore_pair x21, x22, JB_X(21)
    restore_pair x23, x24, JB_X(23)
    restore_pair x25, x26, JB_X(25)
    restore_pair x27, x28, JB_X(27)
    restore_pair  d8,  d9, JB_D(8)
    restore_pair d10, d11, JB_D(10)
    restore_pair d12, d13, JB_D(12)
    restore_pair d14, d15, JB_D(14)

    // Restore SP last.
    // After this, our CFA is setjmp's CFA rather than longjmp's CFA.
    str x2, [x0, #ZX_TLS_UNSAFE_SP_OFFSET]
    //.cfi_same_value unsafe_sp  -- No DWARF register number for it!
    mov sp, x3
    .cfi_same_value sp

    // Don't leak the raw SP values.
    mov x2, xzr
    mov x3, xzr

    // w0 = w1 ?: 1
    cmp w1, wzr
    csinc w0, w1, wzr, ne

    ret

END(longjmp)

ALIAS(longjmp, _longjmp)
WEAK_ALIAS(longjmp, siglongjmp)
