/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
 *
 * Copyright (c) 2008-2013 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */
 
//
// processor specific parsing of dwarf unwind instructions
//

#ifndef __DWARF_INSTRUCTIONS_HPP__
#define __DWARF_INSTRUCTIONS_HPP__

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <algorithm>
#include <vector>

#include <libunwind.h>
#include <mach-o/compact_unwind_encoding.h>

#include "dwarf2.h"
#include "AddressSpace.hpp"
#include "Registers.hpp"
#include "DwarfParser.hpp"
#include "InternalMacros.h"
//#include "CompactUnwinder.hpp"

#define EXTRACT_BITS(value, mask) \
	( (value >> __builtin_ctz(mask)) & (((1 << __builtin_popcount(mask)))-1) )

#define CFI_INVALID_ADDRESS ((pint_t)(-1))

namespace libunwind {

///
/// Used by linker when parsing __eh_frame section
///  
template <typename A>
struct CFI_Reference {
	typedef typename A::pint_t		pint_t;	
	uint8_t		encodingOfTargetAddress;
	uint32_t	offsetInCFI;
	pint_t		targetAddress;
};
template <typename A>
struct CFI_Atom_Info {
	typedef typename A::pint_t		pint_t;	
	pint_t			address;
	uint32_t		size;
	bool			isCIE;
	union {
		struct {
			CFI_Reference<A>	function;
			CFI_Reference<A>	cie;
			CFI_Reference<A>	lsda;
			uint32_t		compactUnwindInfo;
		}			fdeInfo;
		struct {
			CFI_Reference<A>	personality;
		}			cieInfo;
	} u;
};

typedef void (*WarnFunc)(void* ref, uint64_t funcAddr, const char* msg);  

///
/// DwarfInstructions maps abtract dwarf unwind instructions to a particular architecture
///  
template <typename A, typename R>
class DwarfInstructions
{
public:
	typedef typename A::pint_t		pint_t;	
	typedef typename A::sint_t		sint_t;	

	static const char* parseCFIs(A& addressSpace, pint_t ehSectionStart, uint32_t sectionLength, 
                            const pint_t cuStarts[], uint32_t cuCount, 
                            bool keepDwarfWhichHasCU, bool forceDwarfConversion, bool neverConvertToCU,
                            CFI_Atom_Info<A>* infos, uint32_t& infosCount, void* ref, WarnFunc warn);


	static compact_unwind_encoding_t createCompactEncodingFromFDE(A& addressSpace, pint_t fdeStart, 
																pint_t* lsda, pint_t* personality,
																char warningBuffer[1024]);

	static int stepWithDwarf(A& addressSpace, pint_t pc, pint_t fdeStart, R& registers);
										
private:

	enum {
		DW_X86_64_RET_ADDR = 16
	};

	enum {
		DW_X86_RET_ADDR = 8
	};

	static pint_t evaluateExpression(pint_t expression, A& addressSpace, const R& registers, pint_t initialStackValue);
	static pint_t getSavedRegister(A& addressSpace, const R& registers, pint_t cfa, 
										const typename CFI_Parser<A>::RegisterLocation& savedReg);
	static double getSavedFloatRegister(A& addressSpace, const R& registers, pint_t cfa, 
										const typename CFI_Parser<A>::RegisterLocation& savedReg);
	static v128 getSavedVectorRegister(A& addressSpace, const R& registers, pint_t cfa, 
										const typename CFI_Parser<A>::RegisterLocation& savedReg);
										
	// x86 specific variants
	static int    lastRestoreReg(const Registers_x86&);
	static bool   isReturnAddressRegister(int regNum, const Registers_x86&);
	static pint_t getCFA(A& addressSpace, const typename CFI_Parser<A>::PrologInfo& prolog, const Registers_x86&);

	static uint32_t getEBPEncodedRegister(uint32_t reg, int32_t regOffsetFromBaseOffset, bool& failure);
	static compact_unwind_encoding_t encodeToUseDwarf(const Registers_x86&);
	static compact_unwind_encoding_t createCompactEncodingFromProlog(A& addressSpace, pint_t funcAddr,
												const Registers_x86&, const typename CFI_Parser<A>::PrologInfo& prolog,
												char warningBuffer[1024]);

	// x86_64 specific variants
	static int    lastRestoreReg(const Registers_x86_64&);
	static bool   isReturnAddressRegister(int regNum, const Registers_x86_64&);
	static pint_t getCFA(A& addressSpace, const typename CFI_Parser<A>::PrologInfo& prolog, const Registers_x86_64&);

	static uint32_t getRBPEncodedRegister(uint32_t reg, int32_t regOffsetFromBaseOffset, bool& failure);
	static compact_unwind_encoding_t encodeToUseDwarf(const Registers_x86_64&);
	static compact_unwind_encoding_t createCompactEncodingFromProlog(A& addressSpace, pint_t funcAddr,
												const Registers_x86_64&, const typename CFI_Parser<A>::PrologInfo& prolog,
												char warningBuffer[1024]);
	
	// ppc specific variants
	static int    lastRestoreReg(const Registers_ppc&);
	static bool   isReturnAddressRegister(int regNum, const Registers_ppc&);
	static pint_t getCFA(A& addressSpace, const typename CFI_Parser<A>::PrologInfo& prolog, const Registers_ppc&);
	static compact_unwind_encoding_t encodeToUseDwarf(const Registers_ppc&);
	static compact_unwind_encoding_t createCompactEncodingFromProlog(A& addressSpace, pint_t funcAddr,
												const Registers_ppc&, const typename CFI_Parser<A>::PrologInfo& prolog,
												char warningBuffer[1024]);
												
	// arm64 specific variants
	static bool   isReturnAddressRegister(int regNum, const Registers_arm64&);
	static int    lastRestoreReg(const Registers_arm64&);
	static pint_t getCFA(A& addressSpace, const typename CFI_Parser<A>::PrologInfo& prolog, const Registers_arm64&);
	static bool checkRegisterPair(uint32_t reg, const typename CFI_Parser<A>::PrologInfo& prolog,
												int& offset, char warningBuffer[1024]);
	static compact_unwind_encoding_t encodeToUseDwarf(const Registers_arm64&);
	static compact_unwind_encoding_t createCompactEncodingFromProlog(A& addressSpace, pint_t funcAddr,
												const Registers_arm64&, const typename CFI_Parser<A>::PrologInfo& prolog,
												char warningBuffer[1024]);
	
};


											

template <typename A, typename R>
const char* DwarfInstructions<A,R>::parseCFIs(A& addressSpace, pint_t ehSectionStart, uint32_t sectionLength, 
                                      const pint_t cuStarts[], uint32_t cuCount,  
                                      bool keepDwarfWhichHasCU,  bool forceDwarfConversion, bool neverConvertToCU,
                                      CFI_Atom_Info<A>* infos, uint32_t& infosCount, void* ref, WarnFunc warn)
{
	typename CFI_Parser<A>::CIE_Info cieInfo;
	CFI_Atom_Info<A>* entry = infos;
	CFI_Atom_Info<A>* end = &infos[infosCount];
	const pint_t ehSectionEnd = ehSectionStart + sectionLength;
	for (pint_t p=ehSectionStart; p < ehSectionEnd; ) {
		pint_t currentCFI = p;
		uint64_t cfiLength = addressSpace.get32(p);
		p += 4;
		if ( cfiLength == 0xffffffff ) {
			// 0xffffffff means length is really next 8 bytes
			cfiLength = addressSpace.get64(p);
			p += 8;
		}
		if ( cfiLength == 0 ) 
			return NULL;	// end marker
		if ( entry >= end )
			return "too little space allocated for parseCFIs";
		pint_t nextCFI = p + cfiLength;
		uint32_t id = addressSpace.get32(p);
		if ( id == 0 ) {
			// is CIE
			const char* err = CFI_Parser<A>::parseCIE(addressSpace, currentCFI, &cieInfo);
			if ( err != NULL ) 
				return err;
			entry->address = currentCFI;
			entry->size = nextCFI - currentCFI;
			entry->isCIE = true;
			entry->u.cieInfo.personality.targetAddress = cieInfo.personality;
			entry->u.cieInfo.personality.offsetInCFI = cieInfo.personalityOffsetInCIE;
			entry->u.cieInfo.personality.encodingOfTargetAddress = cieInfo.personalityEncoding;
			++entry;
		}
		else {
			// is FDE
			entry->address = currentCFI;
			entry->size = nextCFI - currentCFI;
			entry->isCIE = false;
			entry->u.fdeInfo.function.targetAddress = CFI_INVALID_ADDRESS;
			entry->u.fdeInfo.cie.targetAddress = CFI_INVALID_ADDRESS;
			entry->u.fdeInfo.lsda.targetAddress = CFI_INVALID_ADDRESS;
			uint32_t ciePointer = addressSpace.get32(p);
			pint_t cieStart = p-ciePointer;
			// validate pointer to CIE is within section
			if ( (cieStart < ehSectionStart) || (cieStart > ehSectionEnd) )
				return "FDE points to CIE outside __eh_frame section";
			// optimize usual case where cie is same for all FDEs
			if ( cieStart != cieInfo.cieStart ) {
				const char* err = CFI_Parser<A>::parseCIE(addressSpace, cieStart, &cieInfo);
				if ( err != NULL ) 
					return err;
			}
			entry->u.fdeInfo.cie.targetAddress = cieStart;
			entry->u.fdeInfo.cie.offsetInCFI = p-currentCFI;
			entry->u.fdeInfo.cie.encodingOfTargetAddress = DW_EH_PE_sdata4 | DW_EH_PE_pcrel;
			p += 4;
			// parse pc begin and range
			pint_t offsetOfFunctionAddress = p-currentCFI;
			pint_t pcStart = addressSpace.getEncodedP(p, nextCFI, cieInfo.pointerEncoding);
			pint_t pcRange = addressSpace.getEncodedP(p, nextCFI, cieInfo.pointerEncoding & 0x0F);
			//fprintf(stderr, "FDE with pcRange [0x%08llX, 0x%08llX)\n",(uint64_t)pcStart, (uint64_t)(pcStart+pcRange));
			entry->u.fdeInfo.function.targetAddress = pcStart;
			entry->u.fdeInfo.function.offsetInCFI = offsetOfFunctionAddress;
			entry->u.fdeInfo.function.encodingOfTargetAddress = cieInfo.pointerEncoding;
			// check for augmentation length
			if ( cieInfo.fdesHaveAugmentationData ) {
				uintptr_t augLen = addressSpace.getULEB128(p, nextCFI);
				pint_t endOfAug = p + augLen;
				if ( cieInfo.lsdaEncoding != 0 ) {
					// peek at value (without indirection).  Zero means no lsda
					pint_t lsdaStart = p;
					if ( addressSpace.getEncodedP(p, nextCFI, cieInfo.lsdaEncoding & 0x0F) != 0 ) {
						// reset pointer and re-parse lsda address
						p = lsdaStart;
						pint_t offsetOfLSDAAddress = p-currentCFI;
						entry->u.fdeInfo.lsda.targetAddress = addressSpace.getEncodedP(p, nextCFI, cieInfo.lsdaEncoding);
						entry->u.fdeInfo.lsda.offsetInCFI = offsetOfLSDAAddress;
						entry->u.fdeInfo.lsda.encodingOfTargetAddress = cieInfo.lsdaEncoding;
					}
				}
				p = endOfAug;
			}
			// See if already is a compact unwind for this address.  
			bool alreadyHaveCU = false;
			for (uint32_t i=0; i < cuCount; ++i) {
				if (cuStarts[i] == entry->u.fdeInfo.function.targetAddress) {
				  alreadyHaveCU = true;
				  break;
				}
			}
			//fprintf(stderr, "FDE for func at 0x%08X, alreadyHaveCU=%d\n", (uint32_t)entry->u.fdeInfo.function.targetAddress, alreadyHaveCU);
			if ( alreadyHaveCU && !forceDwarfConversion ) {
				if ( keepDwarfWhichHasCU )
					++entry;
			}
			else {
				if ( neverConvertToCU || ((cuCount != 0) && !forceDwarfConversion) ) {
					// Have some compact unwind, so this is a new .o file, therefore anything without
					// compact unwind must be something not expressable in compact unwind.
					R dummy;
					entry->u.fdeInfo.compactUnwindInfo = encodeToUseDwarf(dummy);
				}
				else {
					// compute compact unwind encoding by parsing dwarf
					typename CFI_Parser<A>::FDE_Info fdeInfo;
					fdeInfo.fdeStart = currentCFI;
					fdeInfo.fdeLength = nextCFI - currentCFI;
					fdeInfo.fdeInstructions = p;
					fdeInfo.pcStart = pcStart;
					fdeInfo.pcEnd = pcStart +  pcRange;
					fdeInfo.lsda = entry->u.fdeInfo.lsda.targetAddress;
					typename CFI_Parser<A>::PrologInfo prolog;
					R dummy; // for proper selection of architecture specific functions
					if ( CFI_Parser<A>::parseFDEInstructions(addressSpace, fdeInfo, cieInfo, CFI_INVALID_ADDRESS, &prolog) ) {
						char warningBuffer[1024];
						entry->u.fdeInfo.compactUnwindInfo = createCompactEncodingFromProlog(addressSpace, fdeInfo.pcStart, dummy, prolog, warningBuffer);
						if ( fdeInfo.lsda != CFI_INVALID_ADDRESS ) 
							entry->u.fdeInfo.compactUnwindInfo |= UNWIND_HAS_LSDA;
						if ( warningBuffer[0] != '\0' )
							warn(ref, fdeInfo.pcStart, warningBuffer);
					}
					else {
						warn(ref, CFI_INVALID_ADDRESS, "dwarf unwind instructions could not be parsed");
						entry->u.fdeInfo.compactUnwindInfo = encodeToUseDwarf(dummy);
					}
				}
				++entry;
			}
		}
		p = nextCFI;
	}
	if ( entry != end ) {
		//fprintf(stderr, "DwarfInstructions<A,R>::parseCFIs() infosCount was %d on input, now %ld\n", infosCount, entry - infos); 
		infosCount = (entry - infos);
	}
	
	return NULL; // success
}




template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::createCompactEncodingFromFDE(A& addressSpace, pint_t fdeStart, 
																		pint_t* lsda, pint_t* personality,
																		char warningBuffer[1024])
{
	typename CFI_Parser<A>::FDE_Info fdeInfo;
	typename CFI_Parser<A>::CIE_Info cieInfo;
	R dummy; // for proper selection of architecture specific functions
	if ( CFI_Parser<A>::decodeFDE(addressSpace, fdeStart, &fdeInfo, &cieInfo) == NULL ) {
		typename CFI_Parser<A>::PrologInfo prolog;
		if ( CFI_Parser<A>::parseFDEInstructions(addressSpace, fdeInfo, cieInfo, CFI_INVALID_ADDRESS, &prolog) ) {
			*lsda = fdeInfo.lsda;
			*personality = cieInfo.personality;
			compact_unwind_encoding_t encoding;
			encoding = createCompactEncodingFromProlog(addressSpace, fdeInfo.pcStart, dummy, prolog, warningBuffer);
			if ( fdeInfo.lsda != 0 ) 
				encoding |= UNWIND_HAS_LSDA;
			return encoding;
		}
		else {
			strcpy(warningBuffer, "dwarf unwind instructions could not be parsed");
			return encodeToUseDwarf(dummy);
		}
	}
	else {
		strcpy(warningBuffer, "dwarf FDE could not be parsed");
		return encodeToUseDwarf(dummy);
	}
}


template <typename A, typename R>
typename A::pint_t DwarfInstructions<A,R>::getSavedRegister(A& addressSpace, const R& registers, pint_t cfa,
													const typename CFI_Parser<A>::RegisterLocation& savedReg)
{
	switch ( savedReg.location ) {
		case CFI_Parser<A>::kRegisterInCFA:
			return addressSpace.getP(cfa + savedReg.value);

		case CFI_Parser<A>::kRegisterAtExpression:
			return addressSpace.getP(evaluateExpression(savedReg.value, addressSpace, registers, cfa));

		case CFI_Parser<A>::kRegisterIsExpression:
			return evaluateExpression(savedReg.value, addressSpace, registers, cfa);

		case CFI_Parser<A>::kRegisterInRegister:
			return registers.getRegister(savedReg.value);

		case CFI_Parser<A>::kRegisterUnused:
		case CFI_Parser<A>::kRegisterOffsetFromCFA:
			// FIX ME
			break;
	}
	ABORT("unsupported restore location for register");
}

template <typename A, typename R>
double DwarfInstructions<A,R>::getSavedFloatRegister(A& addressSpace, const R& registers, pint_t cfa,
													const typename CFI_Parser<A>::RegisterLocation& savedReg)
{
	switch ( savedReg.location ) {
		case CFI_Parser<A>::kRegisterInCFA:
			return addressSpace.getDouble(cfa + savedReg.value);

		case CFI_Parser<A>::kRegisterAtExpression:
			return addressSpace.getDouble(evaluateExpression(savedReg.value, addressSpace, registers, cfa));

		case CFI_Parser<A>::kRegisterIsExpression:
		case CFI_Parser<A>::kRegisterUnused:
		case CFI_Parser<A>::kRegisterOffsetFromCFA:
		case CFI_Parser<A>::kRegisterInRegister:
			// FIX ME
			break;
	}
	ABORT("unsupported restore location for float register");
}

template <typename A, typename R>
v128 DwarfInstructions<A,R>::getSavedVectorRegister(A& addressSpace, const R& registers, pint_t cfa,
													const typename CFI_Parser<A>::RegisterLocation& savedReg)
{
	switch ( savedReg.location ) {
		case CFI_Parser<A>::kRegisterInCFA:
			return addressSpace.getVector(cfa + savedReg.value);

		case CFI_Parser<A>::kRegisterAtExpression:
			return addressSpace.getVector(evaluateExpression(savedReg.value, addressSpace, registers, cfa));

		case CFI_Parser<A>::kRegisterIsExpression:
		case CFI_Parser<A>::kRegisterUnused:
		case CFI_Parser<A>::kRegisterOffsetFromCFA:
		case CFI_Parser<A>::kRegisterInRegister:
			// FIX ME
			break;
	}
	ABORT("unsupported restore location for vector register");
}


template <typename A, typename R>
int DwarfInstructions<A,R>::stepWithDwarf(A& addressSpace, pint_t pc, pint_t fdeStart, R& registers)
{
	//fprintf(stderr, "stepWithDwarf(pc=0x%0llX, fdeStart=0x%0llX)\n", (uint64_t)pc, (uint64_t)fdeStart);
	typename CFI_Parser<A>::FDE_Info fdeInfo;
	typename CFI_Parser<A>::CIE_Info cieInfo;
	if ( CFI_Parser<A>::decodeFDE(addressSpace, fdeStart, &fdeInfo, &cieInfo) == NULL ) {
		typename CFI_Parser<A>::PrologInfo prolog;
		if ( CFI_Parser<A>::parseFDEInstructions(addressSpace, fdeInfo, cieInfo, pc, &prolog) ) {
			R newRegisters = registers;
			
			// get pointer to cfa (architecture specific)
			pint_t cfa = getCFA(addressSpace, prolog, registers);

			// restore registers that dwarf says were saved
			pint_t returnAddress = 0;
			for (int i=0; i <= lastRestoreReg(newRegisters); ++i) {
				if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterUnused ) {
					if ( registers.validFloatRegister(i) )
						newRegisters.setFloatRegister(i, getSavedFloatRegister(addressSpace, registers, cfa, prolog.savedRegisters[i]));
					else if ( registers.validVectorRegister(i) )
						newRegisters.setVectorRegister(i, getSavedVectorRegister(addressSpace, registers, cfa, prolog.savedRegisters[i]));
					else if ( isReturnAddressRegister(i, registers) )
						returnAddress = getSavedRegister(addressSpace, registers, cfa, prolog.savedRegisters[i]);
					else if ( registers.validRegister(i) )
						newRegisters.setRegister(i, getSavedRegister(addressSpace, registers, cfa, prolog.savedRegisters[i]));
					else
						return UNW_EBADREG;
				}
			}
			
			// by definition the CFA is the stack pointer at the call site, so restoring SP means setting it to CFA
			newRegisters.setSP(cfa);

			// return address is address after call site instruction, so setting IP to that does a return
			newRegisters.setIP(returnAddress);
			
			// do the actual step by replacing the register set with the new ones
			registers = newRegisters;

			return UNW_STEP_SUCCESS;
		}
	}
	return UNW_EBADFRAME;
}



template <typename A, typename R>
typename A::pint_t DwarfInstructions<A,R>::evaluateExpression(pint_t expression, A& addressSpace, 
														const R& registers, pint_t initialStackValue)
{
	const bool log = false;
	pint_t p = expression;
	pint_t expressionEnd = expression+20; // just need something until length is read
	uint64_t length = addressSpace.getULEB128(p, expressionEnd);
	expressionEnd = p + length;
	if (log) fprintf(stderr, "evaluateExpression(): length=%llu\n", length);
	pint_t stack[100];
	pint_t* sp = stack;
	*(++sp) = initialStackValue;
	
	while ( p < expressionEnd ) {
		if (log) {
			for(pint_t* t = sp; t > stack; --t) {
				fprintf(stderr, "sp[] = 0x%llX\n", (uint64_t)(*t));
			}
		}
		uint8_t opcode = addressSpace.get8(p++);
		sint_t svalue;
		pint_t value;
		uint32_t reg;
		switch (opcode) {
			case DW_OP_addr:
				// push immediate address sized value
				value = addressSpace.getP(p);
				p += sizeof(pint_t);
				*(++sp) = value;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)value);
				break;
		
			case DW_OP_deref:
				// pop stack, dereference, push result
				value = *sp--;
				*(++sp) = addressSpace.getP(value);
				if (log) fprintf(stderr, "dereference 0x%llX\n", (uint64_t)value);
				break;
		
			case DW_OP_const1u:
				// push immediate 1 byte value
				value = addressSpace.get8(p);
				p += 1;
				*(++sp) = value;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)value);
				break;
				
			case DW_OP_const1s:
				// push immediate 1 byte signed value
				svalue = (int8_t)addressSpace.get8(p);
				p += 1;
				*(++sp) = svalue;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)svalue);
				break;
		
			case DW_OP_const2u:
				// push immediate 2 byte value
				value = addressSpace.get16(p);
				p += 2;
				*(++sp) = value;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)value);
				break;
				
			case DW_OP_const2s:
				// push immediate 2 byte signed value
				svalue = (int16_t)addressSpace.get16(p);
				p += 2;
				*(++sp) = svalue;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)svalue);
				break;
		
			case DW_OP_const4u:
				// push immediate 4 byte value
				value = addressSpace.get32(p);
				p += 4;
				*(++sp) = value;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)value);
				break;
				
			case DW_OP_const4s:
				// push immediate 4 byte signed value
				svalue = (int32_t)addressSpace.get32(p);
				p += 4;
				*(++sp) = svalue;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)svalue);
				break;
				
			case DW_OP_const8u:
				// push immediate 8 byte value
				value = addressSpace.get64(p);
				p += 8;
				*(++sp) = value;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)value);
				break;
				
			case DW_OP_const8s:
				// push immediate 8 byte signed value
				value = (int32_t)addressSpace.get64(p);
				p += 8;
				*(++sp) = value;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)value);
				break;
		
			case DW_OP_constu:
				// push immediate ULEB128 value
				value = addressSpace.getULEB128(p, expressionEnd);
				*(++sp) = value;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)value);
				break;
				
			case DW_OP_consts:
				// push immediate SLEB128 value
				svalue = addressSpace.getSLEB128(p, expressionEnd);
				*(++sp) = svalue;
				if (log) fprintf(stderr, "push 0x%llX\n", (uint64_t)svalue);
				break;
		
			case DW_OP_dup:
				// push top of stack
				value = *sp;
				*(++sp) = value;
				if (log) fprintf(stderr, "duplicate top of stack\n");
				break;
				
			case DW_OP_drop:
				// pop
				--sp; 
				if (log) fprintf(stderr, "pop top of stack\n");
				break;
				
			case DW_OP_over:
				// dup second
				value = sp[-1];
				*(++sp) = value;
				if (log) fprintf(stderr, "duplicate second in stack\n");
				break;

			case DW_OP_pick:
				// pick from
				reg = addressSpace.get8(p);
				p += 1;
				value = sp[-reg];
				*(++sp) = value;
				if (log) fprintf(stderr, "duplicate %d in stack\n", reg);
				break;

			case DW_OP_swap:
				// swap top two
				value = sp[0];
				sp[0] = sp[-1];
				sp[-1] = value;
				if (log) fprintf(stderr, "swap top of stack\n");
				break;

			case DW_OP_rot:
				// rotate top three
				value = sp[0];
				sp[0] = sp[-1];
				sp[-1] = sp[-2];
				sp[-2] = value;
				if (log) fprintf(stderr, "rotate top three of stack\n");
				break;

			case DW_OP_xderef:
				// pop stack, dereference, push result
				value = *sp--;
				*sp = *((uint64_t*)value);
				if (log) fprintf(stderr, "x-dereference 0x%llX\n", (uint64_t)value);
				break;
			
			case DW_OP_abs:
				svalue = *sp;
				if ( svalue < 0 )
					*sp = -svalue;
				if (log) fprintf(stderr, "abs\n");
				break;
		
			case DW_OP_and:
				value = *sp--;
				*sp &= value;
				if (log) fprintf(stderr, "and\n");
				break;
			
			case DW_OP_div:
				svalue = *sp--;
				*sp = *sp / svalue;
				if (log) fprintf(stderr, "div\n");
				break;
			
			case DW_OP_minus:
				svalue = *sp--;
				*sp = *sp - svalue;
				if (log) fprintf(stderr, "minus\n");
				break;

			case DW_OP_mod:
				svalue = *sp--;
				*sp = *sp % svalue;
				if (log) fprintf(stderr, "module\n");
				break;

			case DW_OP_mul:
				svalue = *sp--;
				*sp = *sp * svalue;
				if (log) fprintf(stderr, "mul\n");
				break;

			case DW_OP_neg:
				*sp =  0 - *sp;
				if (log) fprintf(stderr, "neg\n");
				break;

			case DW_OP_not:
				svalue = *sp;
				*sp =  ~svalue;
				if (log) fprintf(stderr, "not\n");
				break;

			case DW_OP_or:
				value = *sp--;
				*sp |= value;
				if (log) fprintf(stderr, "or\n");
				break;

			case DW_OP_plus:
				value = *sp--;
				*sp += value;
				if (log) fprintf(stderr, "plus\n");
				break;

			case DW_OP_plus_uconst:
				// pop stack, add uelb128 constant, push result
				*sp += addressSpace.getULEB128(p, expressionEnd);
				if (log) fprintf(stderr, "add constant\n");
				break;
		
			case DW_OP_shl:
				value = *sp--;
				*sp = *sp << value;
				if (log) fprintf(stderr, "shift left\n");
				break;
			
			case DW_OP_shr:
				value = *sp--;
				*sp = *sp >> value;
				if (log) fprintf(stderr, "shift left\n");
				break;
				
			case DW_OP_shra:
				value = *sp--;
				svalue = *sp;
				*sp = svalue >> value;
				if (log) fprintf(stderr, "shift left arithmetric\n");
				break;
			
			case DW_OP_xor:
				value = *sp--;
				*sp ^= value;
				if (log) fprintf(stderr, "xor\n");
				break;

			case DW_OP_skip:
				svalue = (int16_t)addressSpace.get16(p);
				p += 2;
				p += svalue;
				if (log) fprintf(stderr, "skip %lld\n", (uint64_t)svalue);
				break;
			
			case DW_OP_bra:
				svalue = (int16_t)addressSpace.get16(p);
				p += 2;
				if ( *sp-- )
					p += svalue;
				if (log) fprintf(stderr, "bra %lld\n", (uint64_t)svalue);
				break;
			
			case DW_OP_eq:
				value = *sp--;
				*sp = (*sp == value);
				if (log) fprintf(stderr, "eq\n");
				break;
			
			case DW_OP_ge:
				value = *sp--;
				*sp = (*sp >= value);
				if (log) fprintf(stderr, "ge\n");
				break;
				
			case DW_OP_gt:
				value = *sp--;
				*sp = (*sp > value);
				if (log) fprintf(stderr, "gt\n");
				break;
				
			case DW_OP_le:
				value = *sp--;
				*sp = (*sp <= value);
				if (log) fprintf(stderr, "le\n");
				break;
				
			case DW_OP_lt:
				value = *sp--;
				*sp = (*sp < value);
				if (log) fprintf(stderr, "lt\n");
				break;
				
			case DW_OP_ne:
				value = *sp--;
				*sp = (*sp != value);
				if (log) fprintf(stderr, "ne\n");
				break;
			
			case DW_OP_lit0:
			case DW_OP_lit1:
			case DW_OP_lit2:
			case DW_OP_lit3:
			case DW_OP_lit4:
			case DW_OP_lit5:
			case DW_OP_lit6:
			case DW_OP_lit7:
			case DW_OP_lit8:
			case DW_OP_lit9:
			case DW_OP_lit10:
			case DW_OP_lit11:
			case DW_OP_lit12:
			case DW_OP_lit13:
			case DW_OP_lit14:
			case DW_OP_lit15:
			case DW_OP_lit16:
			case DW_OP_lit17:
			case DW_OP_lit18:
			case DW_OP_lit19:
			case DW_OP_lit20:
			case DW_OP_lit21:
			case DW_OP_lit22:
			case DW_OP_lit23:
			case DW_OP_lit24:
			case DW_OP_lit25:
			case DW_OP_lit26:
			case DW_OP_lit27:
			case DW_OP_lit28:
			case DW_OP_lit29:
			case DW_OP_lit30:
			case DW_OP_lit31:
				value = opcode - DW_OP_lit0;
				*(++sp) = value;
				if (log) fprintf(stderr, "push literal 0x%llX\n", (uint64_t)value);
				break;
		
			case DW_OP_reg0:
			case DW_OP_reg1:
			case DW_OP_reg2:
			case DW_OP_reg3:
			case DW_OP_reg4:
			case DW_OP_reg5:
			case DW_OP_reg6:
			case DW_OP_reg7:
			case DW_OP_reg8:
			case DW_OP_reg9:
			case DW_OP_reg10:
			case DW_OP_reg11:
			case DW_OP_reg12:
			case DW_OP_reg13:
			case DW_OP_reg14:
			case DW_OP_reg15:
			case DW_OP_reg16:
			case DW_OP_reg17:
			case DW_OP_reg18:
			case DW_OP_reg19:
			case DW_OP_reg20:
			case DW_OP_reg21:
			case DW_OP_reg22:
			case DW_OP_reg23:
			case DW_OP_reg24:
			case DW_OP_reg25:
			case DW_OP_reg26:
			case DW_OP_reg27:
			case DW_OP_reg28:
			case DW_OP_reg29:
			case DW_OP_reg30:
			case DW_OP_reg31:
				reg = opcode - DW_OP_reg0;
				*(++sp) = registers.getRegister(reg);
				if (log) fprintf(stderr, "push reg %d\n", reg);
				break;
		
			case DW_OP_regx:
				reg = addressSpace.getULEB128(p, expressionEnd);
				*(++sp) = registers.getRegister(reg);
				if (log) fprintf(stderr, "push reg %d + 0x%llX\n", reg, (uint64_t)svalue);
				break;			

			case DW_OP_breg0:
			case DW_OP_breg1:
			case DW_OP_breg2:
			case DW_OP_breg3:
			case DW_OP_breg4:
			case DW_OP_breg5:
			case DW_OP_breg6:
			case DW_OP_breg7:
			case DW_OP_breg8:
			case DW_OP_breg9:
			case DW_OP_breg10:
			case DW_OP_breg11:
			case DW_OP_breg12:
			case DW_OP_breg13:
			case DW_OP_breg14:
			case DW_OP_breg15:
			case DW_OP_breg16:
			case DW_OP_breg17:
			case DW_OP_breg18:
			case DW_OP_breg19:
			case DW_OP_breg20:
			case DW_OP_breg21:
			case DW_OP_breg22:
			case DW_OP_breg23:
			case DW_OP_breg24:
			case DW_OP_breg25:
			case DW_OP_breg26:
			case DW_OP_breg27:
			case DW_OP_breg28:
			case DW_OP_breg29:
			case DW_OP_breg30:
			case DW_OP_breg31:
				reg = opcode - DW_OP_breg0;
				svalue = addressSpace.getSLEB128(p, expressionEnd);
				*(++sp) = registers.getRegister(reg) + svalue;
				if (log) fprintf(stderr, "push reg %d + 0x%llX\n", reg, (uint64_t)svalue);
				break;
			
			case DW_OP_bregx:
				reg = addressSpace.getULEB128(p, expressionEnd);
				svalue = addressSpace.getSLEB128(p, expressionEnd);
				*(++sp) = registers.getRegister(reg) + svalue;
				if (log) fprintf(stderr, "push reg %d + 0x%llX\n", reg, (uint64_t)svalue);
				break;
			
			case DW_OP_fbreg:
				ABORT("DW_OP_fbreg not implemented");
				break;
				
			case DW_OP_piece:
				ABORT("DW_OP_piece not implemented");
				break;
				
			case DW_OP_deref_size:
				// pop stack, dereference, push result
				value = *sp--;
				switch ( addressSpace.get8(p++) ) {
					case 1:
						value = addressSpace.get8(value);
						break;
					case 2:
						value = addressSpace.get16(value);
						break;
					case 4:
						value = addressSpace.get32(value);
						break;
					case 8:
						value = addressSpace.get64(value);
						break;
					default:
						ABORT("DW_OP_deref_size with bad size");
				}
				*(++sp) = value;
				if (log) fprintf(stderr, "sized dereference 0x%llX\n", (uint64_t)value);
				break;
			
			case DW_OP_xderef_size:
			case DW_OP_nop:
			case DW_OP_push_object_addres:
			case DW_OP_call2:
			case DW_OP_call4:
			case DW_OP_call_ref:
			default:
				ABORT("dwarf opcode not implemented");
		}
	
	}
	if (log) fprintf(stderr, "expression evaluates to 0x%llX\n", (uint64_t)*sp);
	return *sp;
}



//
//	x86_64 specific functions
//

template <typename A, typename R>
int DwarfInstructions<A,R>::lastRestoreReg(const Registers_x86_64&) 
{
	COMPILE_TIME_ASSERT( (int)CFI_Parser<A>::kMaxRegisterNumber > (int)DW_X86_64_RET_ADDR );
	return DW_X86_64_RET_ADDR; 
}

template <typename A, typename R>
bool DwarfInstructions<A,R>::isReturnAddressRegister(int regNum, const Registers_x86_64&) 
{
	return (regNum == DW_X86_64_RET_ADDR); 
}

template <typename A, typename R>
typename A::pint_t DwarfInstructions<A,R>::getCFA(A& addressSpace, const typename CFI_Parser<A>::PrologInfo& prolog, 
										const Registers_x86_64& registers)
{
	if ( prolog.cfaRegister != 0 )
		return registers.getRegister(prolog.cfaRegister) + prolog.cfaRegisterOffset;
	else if ( prolog.cfaExpression != 0 )
		return evaluateExpression(prolog.cfaExpression, addressSpace, registers, 0);
	else
		ABORT("getCFA(): unknown location for x86_64 cfa");
}



template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::encodeToUseDwarf(const Registers_x86_64&) 
{
	return UNWIND_X86_64_MODE_DWARF;
}

template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::encodeToUseDwarf(const Registers_x86&) 
{
	return UNWIND_X86_MODE_DWARF;
}



template <typename A, typename R>
uint32_t DwarfInstructions<A,R>::getRBPEncodedRegister(uint32_t reg, int32_t regOffsetFromBaseOffset, bool& failure)
{
	if ( (regOffsetFromBaseOffset < 0) || (regOffsetFromBaseOffset > 32) ) {
		failure = true;
		return 0;
	}
	unsigned int slotIndex = regOffsetFromBaseOffset/8;
	
	switch ( reg ) {
		case UNW_X86_64_RBX:
			return UNWIND_X86_64_REG_RBX << (slotIndex*3);
		case UNW_X86_64_R12:
			return UNWIND_X86_64_REG_R12 << (slotIndex*3);
		case UNW_X86_64_R13:
			return UNWIND_X86_64_REG_R13 << (slotIndex*3);
		case UNW_X86_64_R14:
			return UNWIND_X86_64_REG_R14 << (slotIndex*3);
		case UNW_X86_64_R15:
			return UNWIND_X86_64_REG_R15 << (slotIndex*3);
	}
	
	// invalid register
	failure = true;
	return 0;
}



template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::createCompactEncodingFromProlog(A& addressSpace, pint_t funcAddr,
												const Registers_x86_64& r, const typename CFI_Parser<A>::PrologInfo& prolog,
												char warningBuffer[1024])
{
	warningBuffer[0] = '\0';
	
	if ( prolog.registerSavedTwiceInCIE == DW_X86_64_RET_ADDR ) {
		warningBuffer[0] = '\0';	// silently disable conversion to compact unwind by linker
		return UNWIND_X86_64_MODE_DWARF;
	}
	// don't create compact unwind info for unsupported dwarf kinds
	if ( prolog.registerSavedMoreThanOnce ) {
		strcpy(warningBuffer, "register saved more than once (might be shrink wrap)");
		return UNWIND_X86_64_MODE_DWARF;
	}
	if ( prolog.cfaOffsetWasNegative ) {
		strcpy(warningBuffer, "cfa had negative offset (dwarf might contain epilog)");
		return UNWIND_X86_64_MODE_DWARF;
	}
	if ( prolog.spExtraArgSize != 0 ) {
		strcpy(warningBuffer, "dwarf uses DW_CFA_GNU_args_size");
		return UNWIND_X86_64_MODE_DWARF;
	}
	if ( prolog.sameValueUsed ) {
		strcpy(warningBuffer, "dwarf uses DW_CFA_same_value");
		return UNWIND_X86_64_MODE_DWARF;
	}

	// figure out which kind of frame this function uses
	bool standardRBPframe = ( 
		 (prolog.cfaRegister == UNW_X86_64_RBP) 
	  && (prolog.cfaRegisterOffset == 16)
	  && (prolog.savedRegisters[UNW_X86_64_RBP].location == CFI_Parser<A>::kRegisterInCFA)
	  && (prolog.savedRegisters[UNW_X86_64_RBP].value == -16) );
	bool standardRSPframe = (prolog.cfaRegister == UNW_X86_64_RSP);
	if ( !standardRBPframe && !standardRSPframe ) {
		// no compact encoding for this
		strcpy(warningBuffer, "does not use RBP or RSP based frame");
		return UNWIND_X86_64_MODE_DWARF;
	}
	
	// scan which registers are saved
	int saveRegisterCount = 0;
	bool rbxSaved = false;
	bool r12Saved = false;
	bool r13Saved = false;
	bool r14Saved = false;
	bool r15Saved = false;
	bool rbpSaved = false;
	for (int i=0; i < 64; ++i) {
		if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterUnused ) {
			if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterInCFA ) {
				sprintf(warningBuffer, "register %d saved somewhere other than in frame", i);
				return UNWIND_X86_64_MODE_DWARF;
			}
			switch (i) {
				case UNW_X86_64_RBX:
					rbxSaved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_64_R12:
					r12Saved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_64_R13:
					r13Saved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_64_R14:
					r14Saved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_64_R15:
					r15Saved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_64_RBP:
					rbpSaved = true;
					++saveRegisterCount;
					break;
				case DW_X86_64_RET_ADDR:
					break;
				default:
					sprintf(warningBuffer, "non-standard register %d being saved in prolog", i);
					return UNWIND_X86_64_MODE_DWARF;
			}
		}
	}
	const int64_t cfaOffsetRBX = prolog.savedRegisters[UNW_X86_64_RBX].value;
	const int64_t cfaOffsetR12 = prolog.savedRegisters[UNW_X86_64_R12].value;
	const int64_t cfaOffsetR13 = prolog.savedRegisters[UNW_X86_64_R13].value;
	const int64_t cfaOffsetR14 = prolog.savedRegisters[UNW_X86_64_R14].value;
	const int64_t cfaOffsetR15 = prolog.savedRegisters[UNW_X86_64_R15].value;
	const int64_t cfaOffsetRBP = prolog.savedRegisters[UNW_X86_64_RBP].value;
	
	// encode standard RBP frames
	compact_unwind_encoding_t  encoding = 0;
	if ( standardRBPframe ) {
		//		|              |
		//		+--------------+   <- CFA
		//		|   ret addr   |
		//		+--------------+
		//		|     rbp      |
		//		+--------------+   <- rbp
		//		~              ~
		//		+--------------+   
		//		|  saved reg3  |
		//		+--------------+   <- CFA - offset+16
		//		|  saved reg2  |
		//		+--------------+   <- CFA - offset+8
		//		|  saved reg1  |
		//		+--------------+   <- CFA - offset
		//		|              |
		//		+--------------+
		//		|              |
		//						   <- rsp
		//
		encoding = UNWIND_X86_64_MODE_RBP_FRAME;
		
		// find save location of farthest register from rbp
		int furthestCfaOffset = 0;
		if ( rbxSaved & (cfaOffsetRBX < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetRBX;
		if ( r12Saved & (cfaOffsetR12 < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetR12;
		if ( r13Saved & (cfaOffsetR13 < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetR13;
		if ( r14Saved & (cfaOffsetR14 < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetR14;
		if ( r15Saved & (cfaOffsetR15 < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetR15;
		
		if ( furthestCfaOffset == 0 ) {
			// no registers saved, nothing more to encode
			return encoding;
		}
		
		// add stack offset to encoding
		int rbpOffset = furthestCfaOffset + 16;
		int encodedOffset = rbpOffset/(-8);
		if ( encodedOffset > 255 ) {
			strcpy(warningBuffer, "offset of saved registers too far to encode");
			return UNWIND_X86_64_MODE_DWARF;
		}
		encoding |= (encodedOffset << __builtin_ctz(UNWIND_X86_64_RBP_FRAME_OFFSET));
		
		// add register saved from each stack location
		bool encodingFailure = false;
		if ( rbxSaved )
			encoding |= getRBPEncodedRegister(UNW_X86_64_RBX, cfaOffsetRBX - furthestCfaOffset, encodingFailure);
		if ( r12Saved )
			encoding |= getRBPEncodedRegister(UNW_X86_64_R12, cfaOffsetR12 - furthestCfaOffset, encodingFailure);
		if ( r13Saved )
			encoding |= getRBPEncodedRegister(UNW_X86_64_R13, cfaOffsetR13 - furthestCfaOffset, encodingFailure);
		if ( r14Saved )
			encoding |= getRBPEncodedRegister(UNW_X86_64_R14, cfaOffsetR14 - furthestCfaOffset, encodingFailure);
		if ( r15Saved )
			encoding |= getRBPEncodedRegister(UNW_X86_64_R15, cfaOffsetR15 - furthestCfaOffset, encodingFailure);
		
		if ( encodingFailure ){
			strcpy(warningBuffer, "saved registers not contiguous");
			return UNWIND_X86_64_MODE_DWARF;
		}

		return encoding;
	}
	else {
		//		|              |
		//		+--------------+   <- CFA
		//		|   ret addr   |
		//		+--------------+
		//		|  saved reg1  |
		//		+--------------+   <- CFA - 16
		//		|  saved reg2  |
		//		+--------------+   <- CFA - 24
		//		|  saved reg3  |
		//		+--------------+   <- CFA - 32
		//		|  saved reg4  |
		//		+--------------+   <- CFA - 40
		//		|  saved reg5  |
		//		+--------------+   <- CFA - 48
		//		|  saved reg6  |
		//		+--------------+   <- CFA - 56
		//		|              |
		//						   <- esp
		//

		// for RSP based frames we need to encode stack size in unwind info
		encoding = UNWIND_X86_64_MODE_STACK_IMMD;
		uint64_t stackValue = prolog.cfaRegisterOffset / 8;
		uint32_t stackAdjust = 0;
		bool immedStackSize = true;
		const uint32_t stackMaxImmedValue = EXTRACT_BITS(0xFFFFFFFF,UNWIND_X86_64_FRAMELESS_STACK_SIZE);
		if ( stackValue > stackMaxImmedValue ) {
			// stack size is too big to fit as an immediate value, so encode offset of subq instruction in function
			if	( prolog.codeOffsetAtStackDecrement == 0 ) {
				strcpy(warningBuffer, "stack size is large but stack subq instruction not found");
				return UNWIND_X86_64_MODE_DWARF;
			}
			pint_t functionContentAdjustStackIns = funcAddr + prolog.codeOffsetAtStackDecrement - 4;
        #if __EXCEPTIONS 
			try {
        #endif
				uint32_t stackDecrementInCode = addressSpace.get32(functionContentAdjustStackIns);
				stackAdjust = (prolog.cfaRegisterOffset - stackDecrementInCode)/8;
        #if __EXCEPTIONS 
			}
			catch (...) {
				strcpy(warningBuffer, "stack size is large but stack subq instruction not found");
				return UNWIND_X86_64_MODE_DWARF;
			}
        #endif
			stackValue = functionContentAdjustStackIns - funcAddr;
			immedStackSize = false;
			if ( stackAdjust > 7 ) {
				strcpy(warningBuffer, "stack subq instruction is too different from dwarf stack size");
				return UNWIND_X86_64_MODE_DWARF;
			}
			encoding = UNWIND_X86_64_MODE_STACK_IND;
		}	
		
		
		// validate that saved registers are all within 6 slots abutting return address
		int registers[6];
		for (int i=0; i < 6;++i)
			registers[i] = 0;
		if ( r15Saved ) {
			if ( cfaOffsetR15 < -56 ) {
				strcpy(warningBuffer, "r15 is saved too far from return address");
				return UNWIND_X86_64_MODE_DWARF;
			}
			registers[(cfaOffsetR15+56)/8] = UNWIND_X86_64_REG_R15;
		}
		if ( r14Saved ) {
			if ( cfaOffsetR14 < -56 ) {
				strcpy(warningBuffer, "r14 is saved too far from return address");
				return UNWIND_X86_64_MODE_DWARF;
			}
			registers[(cfaOffsetR14+56)/8] = UNWIND_X86_64_REG_R14;
		}
		if ( r13Saved ) {
			if ( cfaOffsetR13 < -56 ) {
				strcpy(warningBuffer, "r13 is saved too far from return address");
				return UNWIND_X86_64_MODE_DWARF;
			}
			registers[(cfaOffsetR13+56)/8] = UNWIND_X86_64_REG_R13;
		}
		if ( r12Saved ) {
			if ( cfaOffsetR12 < -56 ) {
				strcpy(warningBuffer, "r12 is saved too far from return address");
				return UNWIND_X86_64_MODE_DWARF;
			}
			registers[(cfaOffsetR12+56)/8] = UNWIND_X86_64_REG_R12;
		}
		if ( rbxSaved ) {
			if ( cfaOffsetRBX < -56 ) {
				strcpy(warningBuffer, "rbx is saved too far from return address");
				return UNWIND_X86_64_MODE_DWARF;
			}
			registers[(cfaOffsetRBX+56)/8] = UNWIND_X86_64_REG_RBX;
		}
		if ( rbpSaved ) {
			if ( cfaOffsetRBP < -56 ) {
				strcpy(warningBuffer, "rbp is saved too far from return address");
				return UNWIND_X86_64_MODE_DWARF;
			}
			registers[(cfaOffsetRBP+56)/8] = UNWIND_X86_64_REG_RBP;
		}
		
		// validate that saved registers are contiguous and abut return address on stack
		for (int i=0; i < saveRegisterCount; ++i) {
			if ( registers[5-i] == 0 ) {
				strcpy(warningBuffer, "registers not save contiguously in stack");
				return UNWIND_X86_64_MODE_DWARF;
			}
		}
				
		// encode register permutation
		// the 10-bits are encoded differently depending on the number of registers saved
		int renumregs[6];
		for (int i=6-saveRegisterCount; i < 6; ++i) {
			int countless = 0;
			for (int j=6-saveRegisterCount; j < i; ++j) {
				if ( registers[j] < registers[i] )
					++countless;
			}
			renumregs[i] = registers[i] - countless -1;
		}
		uint32_t permutationEncoding = 0;
		switch ( saveRegisterCount ) {
			case 6:
				permutationEncoding |= (120*renumregs[0] + 24*renumregs[1] + 6*renumregs[2] + 2*renumregs[3] + renumregs[4]);
				break;
			case 5:
				permutationEncoding |= (120*renumregs[1] + 24*renumregs[2] + 6*renumregs[3] + 2*renumregs[4] + renumregs[5]);
				break;
			case 4:
				permutationEncoding |= (60*renumregs[2] + 12*renumregs[3] + 3*renumregs[4] + renumregs[5]);
				break;
			case 3:
				permutationEncoding |= (20*renumregs[3] + 4*renumregs[4] + renumregs[5]);
				break;
			case 2:
				permutationEncoding |= (5*renumregs[4] + renumregs[5]);
				break;
			case 1:
				permutationEncoding |= (renumregs[5]);
				break;
		}
		
		encoding |= (stackValue << __builtin_ctz(UNWIND_X86_64_FRAMELESS_STACK_SIZE));
		encoding |= (stackAdjust << __builtin_ctz(UNWIND_X86_64_FRAMELESS_STACK_ADJUST));
		encoding |= (saveRegisterCount << __builtin_ctz(UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT));
		encoding |= (permutationEncoding << __builtin_ctz(UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION));
		return encoding;
	}
}




//
//	x86 specific functions
//
template <typename A, typename R>
int DwarfInstructions<A,R>::lastRestoreReg(const Registers_x86&) 
{
	COMPILE_TIME_ASSERT( (int)CFI_Parser<A>::kMaxRegisterNumber > (int)DW_X86_RET_ADDR );
	return DW_X86_RET_ADDR; 
}

template <typename A, typename R>
bool DwarfInstructions<A,R>::isReturnAddressRegister(int regNum, const Registers_x86&) 
{
	return (regNum == DW_X86_RET_ADDR); 
}

template <typename A, typename R>
typename A::pint_t DwarfInstructions<A,R>::getCFA(A& addressSpace, const typename CFI_Parser<A>::PrologInfo& prolog, 
										const Registers_x86& registers)
{
	if ( prolog.cfaRegister != 0 )
		return registers.getRegister(prolog.cfaRegister) + prolog.cfaRegisterOffset;
	else if ( prolog.cfaExpression != 0 )
		return evaluateExpression(prolog.cfaExpression, addressSpace, registers, 0);
	else
		ABORT("getCFA(): unknown location for x86 cfa");
}





template <typename A, typename R>
uint32_t DwarfInstructions<A,R>::getEBPEncodedRegister(uint32_t reg, int32_t regOffsetFromBaseOffset, bool& failure)
{
	if ( (regOffsetFromBaseOffset < 0) || (regOffsetFromBaseOffset > 16) ) {
		failure = true;
		return 0;
	}
	unsigned int slotIndex = regOffsetFromBaseOffset/4;
	
	switch ( reg ) {
		case UNW_X86_EBX:
			return UNWIND_X86_REG_EBX << (slotIndex*3);
		case UNW_X86_ECX:
			return UNWIND_X86_REG_ECX << (slotIndex*3);
		case UNW_X86_EDX:
			return UNWIND_X86_REG_EDX << (slotIndex*3);
		case UNW_X86_EDI:
			return UNWIND_X86_REG_EDI << (slotIndex*3);
		case UNW_X86_ESI:
			return UNWIND_X86_REG_ESI << (slotIndex*3);
	}
	
	// invalid register
	failure = true;
	return 0;
}

template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::createCompactEncodingFromProlog(A& addressSpace, pint_t funcAddr,
												const Registers_x86& r, const typename CFI_Parser<A>::PrologInfo& prolog,
												char warningBuffer[1024])
{
	warningBuffer[0] = '\0';
	
	if ( prolog.registerSavedTwiceInCIE == DW_X86_RET_ADDR ) {
		warningBuffer[0] = '\0';	// silently disable conversion to compact unwind by linker
		return UNWIND_X86_64_MODE_DWARF;
	}
	// don't create compact unwind info for unsupported dwarf kinds
	if ( prolog.registerSavedMoreThanOnce ) {
		strcpy(warningBuffer, "register saved more than once (might be shrink wrap)");
		return UNWIND_X86_MODE_DWARF;
	}
	if ( prolog.spExtraArgSize != 0 ) {
		strcpy(warningBuffer, "dwarf uses DW_CFA_GNU_args_size");
		return UNWIND_X86_MODE_DWARF;
	}
	if ( prolog.sameValueUsed ) {
		strcpy(warningBuffer, "dwarf uses DW_CFA_same_value");
		return UNWIND_X86_MODE_DWARF;
	}
	
	// figure out which kind of frame this function uses
	bool standardEBPframe = ( 
		 (prolog.cfaRegister == UNW_X86_EBP) 
	  && (prolog.cfaRegisterOffset == 8)
	  && (prolog.savedRegisters[UNW_X86_EBP].location == CFI_Parser<A>::kRegisterInCFA)
	  && (prolog.savedRegisters[UNW_X86_EBP].value == -8) );
	bool standardESPframe = (prolog.cfaRegister == UNW_X86_ESP);
	if ( !standardEBPframe && !standardESPframe ) {
		// no compact encoding for this
		strcpy(warningBuffer, "does not use EBP or ESP based frame");
		return UNWIND_X86_MODE_DWARF;
	}
	
	// scan which registers are saved
	int saveRegisterCount = 0;
	bool ebxSaved = false;
	bool ecxSaved = false;
	bool edxSaved = false;
	bool esiSaved = false;
	bool ediSaved = false;
	bool ebpSaved = false;
	for (int i=0; i < 64; ++i) {
		if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterUnused ) {
			if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterInCFA ) {
				sprintf(warningBuffer, "register %d saved somewhere other than in frame", i);
				return UNWIND_X86_MODE_DWARF;
			}
			switch (i) {
				case UNW_X86_EBX:
					ebxSaved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_ECX:
					ecxSaved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_EDX:
					edxSaved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_ESI:
					esiSaved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_EDI:
					ediSaved = true;
					++saveRegisterCount;
					break;
				case UNW_X86_EBP:
					ebpSaved = true;
					++saveRegisterCount;
					break;
				case DW_X86_RET_ADDR:
					break;
				default:
					sprintf(warningBuffer, "non-standard register %d being saved in prolog", i);
					return UNWIND_X86_MODE_DWARF;
			}
		}
	}
	const int32_t cfaOffsetEBX = prolog.savedRegisters[UNW_X86_EBX].value;
	const int32_t cfaOffsetECX = prolog.savedRegisters[UNW_X86_ECX].value;
	const int32_t cfaOffsetEDX = prolog.savedRegisters[UNW_X86_EDX].value;
	const int32_t cfaOffsetEDI = prolog.savedRegisters[UNW_X86_EDI].value;
	const int32_t cfaOffsetESI = prolog.savedRegisters[UNW_X86_ESI].value;
	const int32_t cfaOffsetEBP = prolog.savedRegisters[UNW_X86_EBP].value;
	
	// encode standard RBP frames
	compact_unwind_encoding_t  encoding = 0;
	if ( standardEBPframe ) {
		//		|              |
		//		+--------------+   <- CFA
		//		|   ret addr   |
		//		+--------------+
		//		|     ebp      |
		//		+--------------+   <- ebp
		//		~              ~
		//		+--------------+   
		//		|  saved reg3  |
		//		+--------------+   <- CFA - offset+8
		//		|  saved reg2  |
		//		+--------------+   <- CFA - offset+e
		//		|  saved reg1  |
		//		+--------------+   <- CFA - offset
		//		|              |
		//		+--------------+
		//		|              |
		//						   <- esp
		//
		encoding = UNWIND_X86_MODE_EBP_FRAME;
		
		// find save location of farthest register from ebp
		int furthestCfaOffset = 0;
		if ( ebxSaved & (cfaOffsetEBX < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetEBX;
		if ( ecxSaved & (cfaOffsetECX < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetECX;
		if ( edxSaved & (cfaOffsetEDX < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetEDX;
		if ( ediSaved & (cfaOffsetEDI < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetEDI;
		if ( esiSaved & (cfaOffsetESI < furthestCfaOffset) )
			furthestCfaOffset = cfaOffsetESI;
		
		if ( furthestCfaOffset == 0 ) {
			// no registers saved, nothing more to encode
			return encoding;
		}
		
		// add stack offset to encoding
		int ebpOffset = furthestCfaOffset + 8;
		int encodedOffset = ebpOffset/(-4);
		if ( encodedOffset > 255 ) {
			strcpy(warningBuffer, "offset of saved registers too far to encode");
			return UNWIND_X86_MODE_DWARF;
		}
		encoding |= (encodedOffset << __builtin_ctz(UNWIND_X86_EBP_FRAME_OFFSET));
		
		// add register saved from each stack location
		bool encodingFailure = false;
		if ( ebxSaved )
			encoding |= getEBPEncodedRegister(UNW_X86_EBX, cfaOffsetEBX - furthestCfaOffset, encodingFailure);
		if ( ecxSaved )
			encoding |= getEBPEncodedRegister(UNW_X86_ECX, cfaOffsetECX - furthestCfaOffset, encodingFailure);
		if ( edxSaved )
			encoding |= getEBPEncodedRegister(UNW_X86_EDX, cfaOffsetEDX - furthestCfaOffset, encodingFailure);
		if ( ediSaved )
			encoding |= getEBPEncodedRegister(UNW_X86_EDI, cfaOffsetEDI - furthestCfaOffset, encodingFailure);
		if ( esiSaved )
			encoding |= getEBPEncodedRegister(UNW_X86_ESI, cfaOffsetESI - furthestCfaOffset, encodingFailure);
		
		if ( encodingFailure ){
			strcpy(warningBuffer, "saved registers not contiguous");
			return UNWIND_X86_MODE_DWARF;
		}

		return encoding;
	}
	else {
		//		|              |
		//		+--------------+   <- CFA
		//		|   ret addr   |
		//		+--------------+
		//		|  saved reg1  |
		//		+--------------+   <- CFA - 8
		//		|  saved reg2  |
		//		+--------------+   <- CFA - 12
		//		|  saved reg3  |
		//		+--------------+   <- CFA - 16
		//		|  saved reg4  |
		//		+--------------+   <- CFA - 20
		//		|  saved reg5  |
		//		+--------------+   <- CFA - 24
		//		|  saved reg6  |
		//		+--------------+   <- CFA - 28
		//		|              |
		//						   <- esp
		//

		// for ESP based frames we need to encode stack size in unwind info
		encoding = UNWIND_X86_MODE_STACK_IMMD;
		uint64_t stackValue = prolog.cfaRegisterOffset / 4;
		uint32_t stackAdjust = 0;
		bool immedStackSize = true;
		const uint32_t stackMaxImmedValue = EXTRACT_BITS(0xFFFFFFFF,UNWIND_X86_FRAMELESS_STACK_SIZE);
		if ( stackValue > stackMaxImmedValue ) {
			// stack size is too big to fit as an immediate value, so encode offset of subq instruction in function
			pint_t functionContentAdjustStackIns = funcAddr + prolog.codeOffsetAtStackDecrement - 4;		
        #if __EXCEPTIONS 
			try {
        #endif
                uint32_t stackDecrementInCode = addressSpace.get32(functionContentAdjustStackIns);
                stackAdjust = (prolog.cfaRegisterOffset - stackDecrementInCode)/4;
        #if __EXCEPTIONS 
			}
			catch (...) {
				strcpy(warningBuffer, "stack size is large but stack subl instruction not found");
				return UNWIND_X86_MODE_DWARF;
			}
        #endif
			stackValue = functionContentAdjustStackIns - funcAddr;
			immedStackSize = false;
			if ( stackAdjust > 7 ) {
				strcpy(warningBuffer, "stack subl instruction is too different from dwarf stack size");
				return UNWIND_X86_MODE_DWARF;
			}
			encoding = UNWIND_X86_MODE_STACK_IND;
		}	
		
		
		// validate that saved registers are all within 6 slots abutting return address
		int registers[6];
		for (int i=0; i < 6;++i)
			registers[i] = 0;
		if ( ebxSaved ) {
			if ( cfaOffsetEBX < -28 ) {
				strcpy(warningBuffer, "ebx is saved too far from return address");
				return UNWIND_X86_MODE_DWARF;
			}
			registers[(cfaOffsetEBX+28)/4] = UNWIND_X86_REG_EBX;
		}
		if ( ecxSaved ) {
			if ( cfaOffsetECX < -28 ) {
				strcpy(warningBuffer, "ecx is saved too far from return address");
				return UNWIND_X86_MODE_DWARF;
			}
			registers[(cfaOffsetECX+28)/4] = UNWIND_X86_REG_ECX;
		}
		if ( edxSaved ) {
			if ( cfaOffsetEDX < -28 ) {
				strcpy(warningBuffer, "edx is saved too far from return address");
				return UNWIND_X86_MODE_DWARF;
			}
			registers[(cfaOffsetEDX+28)/4] = UNWIND_X86_REG_EDX;
		}
		if ( ediSaved ) {
			if ( cfaOffsetEDI < -28 ) {
				strcpy(warningBuffer, "edi is saved too far from return address");
				return UNWIND_X86_MODE_DWARF;
			}
			registers[(cfaOffsetEDI+28)/4] = UNWIND_X86_REG_EDI;
		}
		if ( esiSaved ) {
			if ( cfaOffsetESI < -28 ) {
				strcpy(warningBuffer, "esi is saved too far from return address");
				return UNWIND_X86_MODE_DWARF;
			}
			registers[(cfaOffsetESI+28)/4] = UNWIND_X86_REG_ESI;
		}
		if ( ebpSaved ) {
			if ( cfaOffsetEBP < -28 ) {
				strcpy(warningBuffer, "ebp is saved too far from return address");
				return UNWIND_X86_MODE_DWARF;
			}
			registers[(cfaOffsetEBP+28)/4] = UNWIND_X86_REG_EBP;
		}
		
		// validate that saved registers are contiguous and abut return address on stack
		for (int i=0; i < saveRegisterCount; ++i) {
			if ( registers[5-i] == 0 ) {
				strcpy(warningBuffer, "registers not save contiguously in stack");
				return UNWIND_X86_MODE_DWARF;
			}
		}
				
		// encode register permutation
		// the 10-bits are encoded differently depending on the number of registers saved
		int renumregs[6];
		for (int i=6-saveRegisterCount; i < 6; ++i) {
			int countless = 0;
			for (int j=6-saveRegisterCount; j < i; ++j) {
				if ( registers[j] < registers[i] )
					++countless;
			}
			renumregs[i] = registers[i] - countless -1;
		}
		uint32_t permutationEncoding = 0;
		switch ( saveRegisterCount ) {
			case 6:
				permutationEncoding |= (120*renumregs[0] + 24*renumregs[1] + 6*renumregs[2] + 2*renumregs[3] + renumregs[4]);
				break;
			case 5:
				permutationEncoding |= (120*renumregs[1] + 24*renumregs[2] + 6*renumregs[3] + 2*renumregs[4] + renumregs[5]);
				break;
			case 4:
				permutationEncoding |= (60*renumregs[2] + 12*renumregs[3] + 3*renumregs[4] + renumregs[5]);
				break;
			case 3:
				permutationEncoding |= (20*renumregs[3] + 4*renumregs[4] + renumregs[5]);
				break;
			case 2:
				permutationEncoding |= (5*renumregs[4] + renumregs[5]);
				break;
			case 1:
				permutationEncoding |= (renumregs[5]);
				break;
		}
		
		encoding |= (stackValue << __builtin_ctz(UNWIND_X86_FRAMELESS_STACK_SIZE));
		encoding |= (stackAdjust << __builtin_ctz(UNWIND_X86_FRAMELESS_STACK_ADJUST));
		encoding |= (saveRegisterCount << __builtin_ctz(UNWIND_X86_FRAMELESS_STACK_REG_COUNT));
		encoding |= (permutationEncoding << __builtin_ctz(UNWIND_X86_FRAMELESS_STACK_REG_PERMUTATION));
		return encoding;
	}
}







//
//	ppc specific functions
//
template <typename A, typename R>
int DwarfInstructions<A,R>::lastRestoreReg(const Registers_ppc&) 
{
	COMPILE_TIME_ASSERT( (int)CFI_Parser<A>::kMaxRegisterNumber > (int)UNW_PPC_SPEFSCR );
	return UNW_PPC_SPEFSCR; 
}

template <typename A, typename R>
bool DwarfInstructions<A,R>::isReturnAddressRegister(int regNum, const Registers_ppc&) 
{
	return (regNum == UNW_PPC_LR); 
}

template <typename A, typename R>
typename A::pint_t DwarfInstructions<A,R>::getCFA(A& addressSpace, const typename CFI_Parser<A>::PrologInfo& prolog, 
										const Registers_ppc& registers)
{	
	if ( prolog.cfaRegister != 0 )
		return registers.getRegister(prolog.cfaRegister) + prolog.cfaRegisterOffset;
	else if ( prolog.cfaExpression != 0 )
		return evaluateExpression(prolog.cfaExpression, addressSpace, registers, 0);
	else
		ABORT("getCFA(): unknown location for ppc cfa");
}


template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::encodeToUseDwarf(const Registers_ppc&) 
{
	return UNWIND_X86_MODE_DWARF;
}


template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::createCompactEncodingFromProlog(A& addressSpace, pint_t funcAddr,
												const Registers_ppc& r, const typename CFI_Parser<A>::PrologInfo& prolog,
												char warningBuffer[1024])
{
	warningBuffer[0] = '\0';
	return UNWIND_X86_MODE_DWARF;
}



//
// arm64 specific functions
//

template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::encodeToUseDwarf(const Registers_arm64&) 
{
	return UNWIND_ARM64_MODE_DWARF;
}

template <typename A, typename R>
bool DwarfInstructions<A,R>::isReturnAddressRegister(int regNum, const Registers_arm64&) 
{
	return (regNum == UNW_ARM64_LR); 
}

template <typename A, typename R>
int DwarfInstructions<A,R>::lastRestoreReg(const Registers_arm64&) 
{
	COMPILE_TIME_ASSERT( (int)CFI_Parser<A>::kMaxRegisterNumber > (int)UNW_ARM64_D31 );
	return UNW_ARM64_D31; 
}


template <typename A, typename R>
typename A::pint_t DwarfInstructions<A,R>::getCFA(A& addressSpace, const typename CFI_Parser<A>::PrologInfo& prolog, 
                                                                               const Registers_arm64& registers)
{
	if ( prolog.cfaRegister != 0 )
		return registers.getRegister(prolog.cfaRegister) + prolog.cfaRegisterOffset;
	else
		ABORT("getCFA(): unsupported location for arm64 cfa");
}

template <typename A, typename R>
bool DwarfInstructions<A,R>::checkRegisterPair(uint32_t reg, const typename CFI_Parser<A>::PrologInfo& prolog,
												int& offset, char warningBuffer[1024])
{
	if ( (prolog.savedRegisters[reg].location != CFI_Parser<A>::kRegisterUnused)
	  || (prolog.savedRegisters[reg+1].location != CFI_Parser<A>::kRegisterUnused) ) {
		if ( prolog.savedRegisters[reg].location != CFI_Parser<A>::kRegisterInCFA ) {
			sprintf(warningBuffer, "register %d saved somewhere other than in frame", reg);
			return false;
		}
		if ( prolog.savedRegisters[reg+1].location != CFI_Parser<A>::kRegisterInCFA ) {
			sprintf(warningBuffer, "register %d saved somewhere other than in frame", reg+1);
			return false;
		}
		if ( prolog.savedRegisters[reg].value != prolog.savedRegisters[reg+1].value + 8 ) {
			sprintf(warningBuffer, "registers %d and %d not saved contiguously in frame", reg, reg+1);
			return false;
		}
		if ( prolog.savedRegisters[reg].value != offset ) {
			sprintf(warningBuffer, "registers %d not saved contiguously in frame", reg);
			return false;
		}
		offset -= 16;
		return true;
	}
	return false;
}


template <typename A, typename R>
compact_unwind_encoding_t DwarfInstructions<A,R>::createCompactEncodingFromProlog(A& addressSpace, pint_t funcAddr,
												const Registers_arm64& r, const typename CFI_Parser<A>::PrologInfo& prolog,
												char warningBuffer[1024])
{
	warningBuffer[0] = '\0';
	
	if ( prolog.registerSavedTwiceInCIE == UNW_ARM64_LR ) {
		warningBuffer[0] = '\0';	// silently disable conversion to compact unwind by linker
		return UNWIND_ARM64_MODE_DWARF;
	}
	// don't create compact unwind info for unsupported dwarf kinds
	if ( prolog.registerSavedMoreThanOnce ) {
		strcpy(warningBuffer, "register saved more than once (might be shrink wrap)");
		return UNWIND_ARM64_MODE_DWARF;
	}
	if ( prolog.spExtraArgSize != 0 ) {
		strcpy(warningBuffer, "dwarf uses DW_CFA_GNU_args_size");
		return UNWIND_ARM64_MODE_DWARF;
	}
	if ( prolog.sameValueUsed ) {
		strcpy(warningBuffer, "dwarf uses DW_CFA_same_value");
		return UNWIND_ARM64_MODE_DWARF;
	}
	
	compact_unwind_encoding_t encoding = 0;
	int offset = 0;

	// figure out which kind of frame this function uses
	bool standardFPframe = ( 
		 (prolog.cfaRegister == UNW_ARM64_FP) 
	  && (prolog.cfaRegisterOffset == 16)
	  && (prolog.savedRegisters[UNW_ARM64_FP].location == CFI_Parser<A>::kRegisterInCFA)
	  && (prolog.savedRegisters[UNW_ARM64_FP].value == -16) 
	  && (prolog.savedRegisters[UNW_ARM64_LR].location == CFI_Parser<A>::kRegisterInCFA)
	  && (prolog.savedRegisters[UNW_ARM64_LR].value == -8) );

	bool standardFrameless = ( prolog.cfaRegister == UNW_ARM64_SP );
	
	if ( standardFrameless ) {
		// verify enough space for registers saved
		int count = 0;
		for (int i=0; i < 96; ++i) {
			if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterUnused ) 
				++count;
		}
		if ( count * 8 > prolog.cfaRegisterOffset ) {
			strcpy(warningBuffer, "saved registers do not fit in stack size");
			return UNWIND_ARM64_MODE_DWARF;
		}
		if ( (prolog.cfaRegisterOffset % 16) != 0 ) {
			strcpy(warningBuffer, "stack size is not 16-byte multiple");
			return UNWIND_ARM64_MODE_DWARF;
		}
		const int32_t maxStack = (UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK >> __builtin_ctz(UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK));
		if ( (prolog.cfaRegisterOffset / 16) > maxStack ) {
			strcpy(warningBuffer, "stack size is too large for frameless function");
			return UNWIND_ARM64_MODE_DWARF;
		}
		encoding = UNWIND_ARM64_MODE_FRAMELESS | ((prolog.cfaRegisterOffset/16) << __builtin_ctz(UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK));
		offset = -16;
	}
	else if ( standardFPframe  ) {
		encoding = UNWIND_ARM64_MODE_FRAME;
		offset = -24;
	}
	else {
		// no compact encoding for this
		strcpy(warningBuffer, "does not use standard frame");
		return UNWIND_ARM64_MODE_DWARF;
	}
		
	// make sure no volatile registers are saved
	for (int i=UNW_ARM64_X0; i < UNW_ARM64_X19; ++i) {
		if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterUnused ) {
			sprintf(warningBuffer, "non standard register %d saved in frame", i);
			return UNWIND_ARM64_MODE_DWARF;
		}
	}
	for (int i=UNW_ARM64_SP+1; i < UNW_ARM64_D8; ++i) {
		if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterUnused ) {
			sprintf(warningBuffer, "non standard register %d saved in frame", i);
			return UNWIND_ARM64_MODE_DWARF;
		}
	}
	for (int i=UNW_ARM64_D16; i < UNW_ARM64_D31+1; ++i) {
		if ( prolog.savedRegisters[i].location != CFI_Parser<A>::kRegisterUnused ) {
			sprintf(warningBuffer, "non standard register %d saved in frame", i);
			return UNWIND_ARM64_MODE_DWARF;
		}
	}

	// compute encoding
	bool X19_X20_saved = checkRegisterPair(UNW_ARM64_X19, prolog, offset, warningBuffer);
	bool X21_X22_saved = checkRegisterPair(UNW_ARM64_X21, prolog, offset, warningBuffer);
	bool X23_X24_saved = checkRegisterPair(UNW_ARM64_X23, prolog, offset, warningBuffer);
	bool X25_X26_saved = checkRegisterPair(UNW_ARM64_X25, prolog, offset, warningBuffer);
	bool X27_X28_saved = checkRegisterPair(UNW_ARM64_X27, prolog, offset, warningBuffer);
	bool D8_D9_saved   = checkRegisterPair(UNW_ARM64_D8,  prolog, offset, warningBuffer);
	bool D10_D11_saved = checkRegisterPair(UNW_ARM64_D10, prolog, offset, warningBuffer);
	bool D12_D13_saved = checkRegisterPair(UNW_ARM64_D12, prolog, offset, warningBuffer);
	bool D14_D15_saved = checkRegisterPair(UNW_ARM64_D14, prolog, offset, warningBuffer);
	if ( warningBuffer[0] != '\0' )
		return UNWIND_ARM64_MODE_DWARF;

	if ( X19_X20_saved )
		encoding |= UNWIND_ARM64_FRAME_X19_X20_PAIR;
	if ( X21_X22_saved )
		encoding |= UNWIND_ARM64_FRAME_X21_X22_PAIR;
	if ( X23_X24_saved )
		encoding |= UNWIND_ARM64_FRAME_X23_X24_PAIR;
	if ( X25_X26_saved )
		encoding |= UNWIND_ARM64_FRAME_X25_X26_PAIR;
	if ( X27_X28_saved )
		encoding |= UNWIND_ARM64_FRAME_X27_X28_PAIR;
	if ( D8_D9_saved )
		encoding |= UNWIND_ARM64_FRAME_D8_D9_PAIR;
	if ( D10_D11_saved )
		encoding |= UNWIND_ARM64_FRAME_D10_D11_PAIR;
	if ( D12_D13_saved )
		encoding |= UNWIND_ARM64_FRAME_D12_D13_PAIR;
	if ( D14_D15_saved )
		encoding |= UNWIND_ARM64_FRAME_D14_D15_PAIR;

  return encoding;
}

} // namespace libunwind


#endif // __DWARF_INSTRUCTIONS_HPP__




