Improve the x86_64 return value decoder to handle most structure returns.

Switch from GetReturnValue, which was hardly ever used, to GetReturnValueObject
which is much more convenient.
Return the "return value object" as a persistent variable if requested.

llvm-svn: 147157
This commit is contained in:
Jim Ingham 2011-12-22 19:12:40 +00:00
parent 6901c0de67
commit ef65160016
19 changed files with 897 additions and 277 deletions

View File

@ -45,14 +45,19 @@ public:
GetArgumentValues (Thread &thread,
ValueList &values) const = 0;
virtual bool
GetReturnValue (Thread &thread,
Value &value) const = 0;
virtual lldb::ValueObjectSP
public:
lldb::ValueObjectSP
GetReturnValueObject (Thread &thread,
ClangASTType &type) const;
ClangASTType &type,
bool persistent = true) const;
protected:
// This is the method the ABI will call to actually calculate the return value.
// Don't put it in a persistant value object, that will be done by the ABI::GetReturnValueObject.
virtual lldb::ValueObjectSP
GetReturnValueObjectImpl (Thread &thread,
ClangASTType &type) const = 0;
public:
virtual bool
CreateFunctionEntryUnwindPlan (UnwindPlan &unwind_plan) = 0;

View File

@ -22,9 +22,13 @@ namespace lldb_private {
class ThreadPlanCallFunction : public ThreadPlan
{
// Create a thread plan to call a function at the address passed in the "function"
// argument. If you plan to call GetReturnValueObject, then pass in the
// return type, otherwise just pass in an invalid ClangASTType.
public:
ThreadPlanCallFunction (Thread &thread,
Address &function,
const ClangASTType &return_type,
lldb::addr_t arg,
bool stop_other_threads,
bool discard_on_error = true,
@ -33,6 +37,7 @@ public:
ThreadPlanCallFunction (Thread &thread,
Address &function,
const ClangASTType &return_type,
bool stop_other_threads,
bool discard_on_error,
lldb::addr_t *arg1_ptr = NULL,
@ -90,16 +95,10 @@ public:
// plan is complete, you can call "GetReturnValue()" to retrieve the value
// that was extracted.
const lldb::ValueSP &
GetReturnValue ()
virtual lldb::ValueObjectSP
GetReturnValueObject ()
{
return m_return_value_sp;
}
void
RequestReturnValue (lldb::ValueSP &return_value_sp)
{
m_return_value_sp = return_value_sp;
return m_return_valobj_sp;
}
// Return the stack pointer that the function received
@ -165,7 +164,8 @@ private:
// thread plans, but for reporting purposes,
// it's nice to know the real stop reason.
// This gets set in DoTakedown.
lldb::ValueSP m_return_value_sp; // If this contains a valid pointer, use the ABI to extract values when complete
ClangASTType m_return_type;
lldb::ValueObjectSP m_return_valobj_sp; // If this contains a valid pointer, use the ABI to extract values when complete
bool m_takedown_done; // We want to ensure we only do the takedown once. This ensures that.
lldb::addr_t m_stop_address; // This is the address we stopped at. Also set in DoTakedown;

View File

@ -1709,12 +1709,9 @@ Debugger::FormatPrompt
ValueObjectSP return_valobj_sp = StopInfo::GetReturnValueObject (stop_info_sp);
if (return_valobj_sp)
{
cstr = return_valobj_sp->GetValueAsCString ();
if (cstr && cstr[0])
{
s.PutCString(cstr);
var_success = true;
}
ValueObject::DumpValueObjectOptions dump_options;
ValueObject::DumpValueObject (s, return_valobj_sp.get(), dump_options);
var_success = true;
}
}
}
@ -2579,7 +2576,7 @@ Debugger::SettingsController::global_settings_table[] =
MODULE_WITH_FUNC\
FILE_AND_LINE\
"{, stop reason = ${thread.stop-reason}}"\
"{, return value = ${thread.return-value}}"\
"{\\nReturn value: ${thread.return-value}}"\
"\\n"
//#define DEFAULT_THREAD_FORMAT "thread #${thread.index}: tid = ${thread.id}"\

View File

@ -405,6 +405,7 @@ ClangFunction::GetThreadPlanToCallFunction (ExecutionContext &exe_ctx,
Address wrapper_address (NULL, func_addr);
ThreadPlan *new_plan = new ThreadPlanCallFunction (*thread,
wrapper_address,
ClangASTType(),
args_addr,
stop_others,
discard_on_error,
@ -418,7 +419,8 @@ ClangFunction::FetchFunctionResults (ExecutionContext &exe_ctx, lldb::addr_t arg
{
// Read the return value - it is the last field in the struct:
// FIXME: How does clang tell us there's no return value? We need to handle that case.
// FIXME: Create our ThreadPlanCallFunction with the return ClangASTType, and then use GetReturnValueObject
// to fetch the value. That way we can fetch any values we need.
Process *process = exe_ctx.GetProcessPtr();
if (process == NULL)

View File

@ -16,6 +16,7 @@
#include "lldb/Core/RegisterValue.h"
#include "lldb/Core/Scalar.h"
#include "lldb/Core/Value.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/Symbol/ClangASTContext.h"
#include "lldb/Symbol/UnwindPlan.h"
#include "lldb/Target/Process.h"
@ -417,85 +418,92 @@ ABIMacOSX_arm::GetArgumentValues (Thread &thread,
return true;
}
bool
ABIMacOSX_arm::GetReturnValue (Thread &thread,
Value &value) const
ValueObjectSP
ABIMacOSX_arm::GetReturnValueObjectImpl (Thread &thread,
lldb_private::ClangASTType &ast_type) const
{
switch (value.GetContextType())
Value value;
ValueObjectSP return_valobj_sp;
void *value_type = ast_type.GetOpaqueQualType();
if (!value_type)
return return_valobj_sp;
clang::ASTContext *ast_context = ast_type.GetASTContext();
if (!ast_context)
return return_valobj_sp;
value.SetContext (Value::eContextTypeClangType, value_type);
RegisterContext *reg_ctx = thread.GetRegisterContext().get();
if (!reg_ctx)
return return_valobj_sp;
bool is_signed;
// Get the pointer to the first stack argument so we have a place to start
// when reading data
const RegisterInfo *r0_reg_info = reg_ctx->GetRegisterInfoByName("r0", 0);
if (ClangASTContext::IsIntegerType (value_type, is_signed))
{
default:
return false;
case Value::eContextTypeClangType:
size_t bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, value_type);
switch (bit_width)
{
// Extract the Clang AST context from the PC so that we can figure out type
// sizes
clang::ASTContext *ast_context = thread.CalculateTarget()->GetScratchClangASTContext()->getASTContext();
// Get the pointer to the first stack argument so we have a place to start
// when reading data
RegisterContext *reg_ctx = thread.GetRegisterContext().get();
void *value_type = value.GetClangType();
bool is_signed;
const RegisterInfo *r0_reg_info = reg_ctx->GetRegisterInfoByName("r0", 0);
if (ClangASTContext::IsIntegerType (value_type, is_signed))
default:
return return_valobj_sp;
case 64:
{
size_t bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, value_type);
switch (bit_width)
{
default:
return false;
case 64:
{
const RegisterInfo *r1_reg_info = reg_ctx->GetRegisterInfoByName("r1", 0);
uint64_t raw_value;
raw_value = reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT32_MAX;
raw_value |= ((uint64_t)(reg_ctx->ReadRegisterAsUnsigned(r1_reg_info, 0) & UINT32_MAX)) << 32;
if (is_signed)
value.GetScalar() = (int64_t)raw_value;
else
value.GetScalar() = (uint64_t)raw_value;
}
break;
case 32:
if (is_signed)
value.GetScalar() = (int32_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT32_MAX);
else
value.GetScalar() = (uint32_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT32_MAX);
break;
case 16:
if (is_signed)
value.GetScalar() = (int16_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT16_MAX);
else
value.GetScalar() = (uint16_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT16_MAX);
break;
case 8:
if (is_signed)
value.GetScalar() = (int8_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT8_MAX);
else
value.GetScalar() = (uint8_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT8_MAX);
break;
}
}
else if (ClangASTContext::IsPointerType (value_type))
{
uint32_t ptr = thread.GetRegisterContext()->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT32_MAX;
value.GetScalar() = ptr;
}
else
{
// not handled yet
return false;
const RegisterInfo *r1_reg_info = reg_ctx->GetRegisterInfoByName("r1", 0);
uint64_t raw_value;
raw_value = reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT32_MAX;
raw_value |= ((uint64_t)(reg_ctx->ReadRegisterAsUnsigned(r1_reg_info, 0) & UINT32_MAX)) << 32;
if (is_signed)
value.GetScalar() = (int64_t)raw_value;
else
value.GetScalar() = (uint64_t)raw_value;
}
break;
case 32:
if (is_signed)
value.GetScalar() = (int32_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT32_MAX);
else
value.GetScalar() = (uint32_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT32_MAX);
break;
case 16:
if (is_signed)
value.GetScalar() = (int16_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT16_MAX);
else
value.GetScalar() = (uint16_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT16_MAX);
break;
case 8:
if (is_signed)
value.GetScalar() = (int8_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT8_MAX);
else
value.GetScalar() = (uint8_t)(reg_ctx->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT8_MAX);
break;
}
break;
}
else if (ClangASTContext::IsPointerType (value_type))
{
uint32_t ptr = thread.GetRegisterContext()->ReadRegisterAsUnsigned(r0_reg_info, 0) & UINT32_MAX;
value.GetScalar() = ptr;
}
else
{
// not handled yet
return return_valobj_sp;
}
return true;
// If we get here, we have a valid Value, so make our ValueObject out of it:
return_valobj_sp = ValueObjectConstResult::Create(
thread.GetStackFrameAtIndex(0).get(),
ast_type.GetASTContext(),
value,
ConstString(""));
return return_valobj_sp;
}
bool

View File

@ -41,10 +41,12 @@ public:
GetArgumentValues (lldb_private::Thread &thread,
lldb_private::ValueList &values) const;
virtual bool
GetReturnValue (lldb_private::Thread &thread,
lldb_private::Value &value) const;
protected:
virtual lldb::ValueObjectSP
GetReturnValueObjectImpl (lldb_private::Thread &thread,
lldb_private::ClangASTType &ast_type) const;
public:
virtual bool
CreateFunctionEntryUnwindPlan (lldb_private::UnwindPlan &unwind_plan);

View File

@ -15,6 +15,7 @@
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/RegisterValue.h"
#include "lldb/Core/Scalar.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/Symbol/ClangASTContext.h"
#include "lldb/Symbol/UnwindPlan.h"
#include "lldb/Target/Process.h"
@ -681,87 +682,91 @@ ABIMacOSX_i386::GetArgumentValues (Thread &thread,
return true;
}
bool
ABIMacOSX_i386::GetReturnValue (Thread &thread,
Value &value) const
ValueObjectSP
ABIMacOSX_i386::GetReturnValueObjectImpl (Thread &thread,
ClangASTType &ast_type) const
{
switch (value.GetContextType())
Value value;
ValueObjectSP return_valobj_sp;
void *value_type = ast_type.GetOpaqueQualType();
if (!value_type)
return return_valobj_sp;
clang::ASTContext *ast_context = ast_type.GetASTContext();
if (!ast_context)
return return_valobj_sp;
value.SetContext (Value::eContextTypeClangType, value_type);
RegisterContext *reg_ctx = thread.GetRegisterContext().get();
if (!reg_ctx)
return return_valobj_sp;
bool is_signed;
if (ClangASTContext::IsIntegerType (value_type, is_signed))
{
default:
return false;
case Value::eContextTypeClangType:
size_t bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, value_type);
unsigned eax_id = reg_ctx->GetRegisterInfoByName("eax", 0)->kinds[eRegisterKindLLDB];
unsigned edx_id = reg_ctx->GetRegisterInfoByName("edx", 0)->kinds[eRegisterKindLLDB];
switch (bit_width)
{
// Extract the Clang AST context from the PC so that we can figure out type
// sizes
clang::ASTContext *ast_context = thread.CalculateTarget()->GetScratchClangASTContext()->getASTContext();
// Get the pointer to the first stack argument so we have a place to start
// when reading data
RegisterContext *reg_ctx = thread.GetRegisterContext().get();
void *value_type = value.GetClangType();
bool is_signed;
if (ClangASTContext::IsIntegerType (value_type, is_signed))
{
size_t bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, value_type);
unsigned eax_id = reg_ctx->GetRegisterInfoByName("eax", 0)->kinds[eRegisterKindLLDB];
unsigned edx_id = reg_ctx->GetRegisterInfoByName("edx", 0)->kinds[eRegisterKindLLDB];
switch (bit_width)
{
default:
case 128:
// Scalar can't hold 128-bit literals, so we don't handle this
return false;
case 64:
uint64_t raw_value;
raw_value = thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffffffff;
raw_value |= (thread.GetRegisterContext()->ReadRegisterAsUnsigned(edx_id, 0) & 0xffffffff) << 32;
if (is_signed)
value.GetScalar() = (int64_t)raw_value;
else
value.GetScalar() = (uint64_t)raw_value;
break;
case 32:
if (is_signed)
value.GetScalar() = (int32_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffffffff);
else
value.GetScalar() = (uint32_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffffffff);
break;
case 16:
if (is_signed)
value.GetScalar() = (int16_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffff);
else
value.GetScalar() = (uint16_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffff);
break;
case 8:
if (is_signed)
value.GetScalar() = (int8_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xff);
else
value.GetScalar() = (uint8_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xff);
break;
}
}
else if (ClangASTContext::IsPointerType (value_type))
{
unsigned eax_id = reg_ctx->GetRegisterInfoByName("eax", 0)->kinds[eRegisterKindLLDB];
uint32_t ptr = thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffffffff;
value.GetScalar() = ptr;
}
else
{
// not handled yet
return false;
}
default:
case 128:
// Scalar can't hold 128-bit literals, so we don't handle this
return return_valobj_sp;
case 64:
uint64_t raw_value;
raw_value = thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffffffff;
raw_value |= (thread.GetRegisterContext()->ReadRegisterAsUnsigned(edx_id, 0) & 0xffffffff) << 32;
if (is_signed)
value.GetScalar() = (int64_t)raw_value;
else
value.GetScalar() = (uint64_t)raw_value;
break;
case 32:
if (is_signed)
value.GetScalar() = (int32_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffffffff);
else
value.GetScalar() = (uint32_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffffffff);
break;
case 16:
if (is_signed)
value.GetScalar() = (int16_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffff);
else
value.GetScalar() = (uint16_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffff);
break;
case 8:
if (is_signed)
value.GetScalar() = (int8_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xff);
else
value.GetScalar() = (uint8_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xff);
break;
}
break;
}
else if (ClangASTContext::IsPointerType (value_type))
{
unsigned eax_id = reg_ctx->GetRegisterInfoByName("eax", 0)->kinds[eRegisterKindLLDB];
uint32_t ptr = thread.GetRegisterContext()->ReadRegisterAsUnsigned(eax_id, 0) & 0xffffffff;
value.GetScalar() = ptr;
}
else
{
// not handled yet
return return_valobj_sp;
}
return true;
// If we get here, we have a valid Value, so make our ValueObject out of it:
return_valobj_sp = ValueObjectConstResult::Create(
thread.GetStackFrameAtIndex(0).get(),
ast_type.GetASTContext(),
value,
ConstString(""));
return return_valobj_sp;
}
bool

View File

@ -51,9 +51,12 @@ public:
GetArgumentValues (lldb_private::Thread &thread,
lldb_private::ValueList &values) const;
virtual bool
GetReturnValue (lldb_private::Thread &thread,
lldb_private::Value &value) const;
protected:
virtual lldb::ValueObjectSP
GetReturnValueObjectImpl (lldb_private::Thread &thread,
lldb_private::ClangASTType &ast_type) const;
public:
virtual bool
CreateFunctionEntryUnwindPlan (lldb_private::UnwindPlan &unwind_plan);

View File

@ -17,6 +17,9 @@
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/RegisterValue.h"
#include "lldb/Core/Value.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/Core/ValueObjectRegister.h"
#include "lldb/Core/ValueObjectMemory.h"
#include "lldb/Symbol/ClangASTContext.h"
#include "lldb/Symbol/UnwindPlan.h"
#include "lldb/Target/Target.h"
@ -547,83 +550,404 @@ ABISysV_x86_64::GetArgumentValues (Thread &thread,
return true;
}
bool
ABISysV_x86_64::GetReturnValue (Thread &thread,
Value &value) const
ValueObjectSP
ABISysV_x86_64::GetReturnValueObjectSimple (Thread &thread,
ClangASTType &ast_type) const
{
switch (value.GetContextType())
ValueObjectSP return_valobj_sp;
Value value;
clang_type_t value_type = ast_type.GetOpaqueQualType();
if (!value_type)
return return_valobj_sp;
clang::ASTContext *ast_context = ast_type.GetASTContext();
if (!ast_context)
return return_valobj_sp;
value.SetContext (Value::eContextTypeClangType, value_type);
RegisterContext *reg_ctx = thread.GetRegisterContext().get();
if (!reg_ctx)
return return_valobj_sp;
bool is_signed;
bool is_complex;
uint32_t count;
if (ClangASTContext::IsIntegerType (value_type, is_signed))
{
default:
return false;
case Value::eContextTypeClangType:
// For now, assume that the types in the AST values come from the Target's
// scratch AST.
// Extract the register context so we can read arguments from registers
size_t bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, value_type);
unsigned rax_id = reg_ctx->GetRegisterInfoByName("rax", 0)->kinds[eRegisterKindLLDB];
switch (bit_width)
{
void *value_type = value.GetClangType();
bool is_signed;
RegisterContext *reg_ctx = thread.GetRegisterContext().get();
if (!reg_ctx)
return false;
if (ClangASTContext::IsIntegerType (value_type, is_signed))
default:
case 128:
// Scalar can't hold 128-bit literals, so we don't handle this
return return_valobj_sp;
case 64:
if (is_signed)
value.GetScalar() = (int64_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0));
else
value.GetScalar() = (uint64_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0));
break;
case 32:
if (is_signed)
value.GetScalar() = (int32_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xffffffff);
else
value.GetScalar() = (uint32_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xffffffff);
break;
case 16:
if (is_signed)
value.GetScalar() = (int16_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xffff);
else
value.GetScalar() = (uint16_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xffff);
break;
case 8:
if (is_signed)
value.GetScalar() = (int8_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xff);
else
value.GetScalar() = (uint8_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xff);
break;
}
}
else if (ClangASTContext::IsFloatingPointType(value_type, count, is_complex))
{
// Don't handle complex yet.
if (is_complex)
return return_valobj_sp;
size_t bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, value_type);
if (bit_width <= 64)
{
const RegisterInfo *xmm0_info = reg_ctx->GetRegisterInfoByName("xmm0", 0);
RegisterValue xmm0_value;
if (reg_ctx->ReadRegister (xmm0_info, xmm0_value))
{
// For now, assume that the types in the AST values come from the Target's
// scratch AST.
clang::ASTContext *ast_context = thread.CalculateTarget()->GetScratchClangASTContext()->getASTContext();
// Extract the register context so we can read arguments from registers
size_t bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, value_type);
unsigned rax_id = reg_ctx->GetRegisterInfoByName("rax", 0)->kinds[eRegisterKindLLDB];
switch (bit_width)
DataExtractor data;
if (xmm0_value.GetData(data))
{
default:
case 128:
// Scalar can't hold 128-bit literals, so we don't handle this
return false;
case 64:
if (is_signed)
value.GetScalar() = (int64_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0));
else
value.GetScalar() = (uint64_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0));
break;
case 32:
if (is_signed)
value.GetScalar() = (int32_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xffffffff);
else
value.GetScalar() = (uint32_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xffffffff);
break;
case 16:
if (is_signed)
value.GetScalar() = (int16_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xffff);
else
value.GetScalar() = (uint16_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xffff);
break;
case 8:
if (is_signed)
value.GetScalar() = (int8_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xff);
else
value.GetScalar() = (uint8_t)(thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0) & 0xff);
break;
uint32_t offset = 0;
switch (bit_width)
{
default:
return return_valobj_sp;
case 32:
value.GetScalar() = (float) data.GetFloat(&offset);
break;
case 64:
value.GetScalar() = (double) data.GetDouble(&offset);
break;
}
}
}
else if (ClangASTContext::IsPointerType (value_type))
{
unsigned rax_id = reg_ctx->GetRegisterInfoByName("rax", 0)->kinds[eRegisterKindLLDB];
value.GetScalar() = (uint64_t)thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0);
}
else
{
// not handled yet
return false;
}
}
break;
else if (bit_width == 128)
{
// FIXME: x86_64 returns long doubles in stmm0, which is in some 80 bit long double
// format, and so we'll have to write some code to convert that into 128 bit long doubles.
// const RegisterInfo *st0_info = reg_ctx->GetRegisterInfoByName("stmm0", 0);
// RegisterValue st0_value;
// if (reg_ctx->ReadRegister (st0_info, st0_value))
// {
// DataExtractor data;
// if (st0_value.GetData(data))
// {
// uint32_t offset = 0;
// value.GetScalar() = (long double) data.GetLongDouble (&offset);
// return true;
// }
// }
return return_valobj_sp;
}
}
else if (ClangASTContext::IsPointerType (value_type))
{
unsigned rax_id = reg_ctx->GetRegisterInfoByName("rax", 0)->kinds[eRegisterKindLLDB];
value.GetScalar() = (uint64_t)thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0);
}
else
{
return return_valobj_sp;
}
return true;
// If we get here, we have a valid Value, so make our ValueObject out of it:
return_valobj_sp = ValueObjectConstResult::Create(
thread.GetStackFrameAtIndex(0).get(),
ast_type.GetASTContext(),
value,
ConstString(""));
return return_valobj_sp;
}
ValueObjectSP
ABISysV_x86_64::GetReturnValueObjectImpl (Thread &thread,
ClangASTType &ast_type) const
{
ValueObjectSP return_valobj_sp;
return_valobj_sp = GetReturnValueObjectSimple(thread, ast_type);
if (return_valobj_sp)
return return_valobj_sp;
clang_type_t ret_value_type = ast_type.GetOpaqueQualType();
if (!ret_value_type)
return return_valobj_sp;
clang::ASTContext *ast_context = ast_type.GetASTContext();
if (!ast_context)
return return_valobj_sp;
RegisterContextSP reg_ctx_sp = thread.GetRegisterContext();
if (!reg_ctx_sp)
return return_valobj_sp;
size_t bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, ret_value_type);
if (ClangASTContext::IsAggregateType(ret_value_type))
{
Target &target = thread.GetProcess().GetTarget();
bool is_memory = true;
if (bit_width <= 128)
{
ByteOrder target_byte_order = target.GetArchitecture().GetByteOrder();
DataBufferSP data_sp (new DataBufferHeap(16, 0));
DataExtractor return_ext (data_sp,
target_byte_order,
target.GetArchitecture().GetAddressByteSize());
const RegisterInfo *rax_info = reg_ctx_sp->GetRegisterInfoByName("rax", 0);
const RegisterInfo *rdx_info = reg_ctx_sp->GetRegisterInfoByName("rdx", 0);
const RegisterInfo *xmm0_info = reg_ctx_sp->GetRegisterInfoByName("xmm0", 0);
const RegisterInfo *xmm1_info = reg_ctx_sp->GetRegisterInfoByName("xmm1", 0);
RegisterValue rax_value, rdx_value, xmm0_value, xmm1_value;
reg_ctx_sp->ReadRegister (rax_info, rax_value);
reg_ctx_sp->ReadRegister (rdx_info, rdx_value);
reg_ctx_sp->ReadRegister (xmm0_info, xmm0_value);
reg_ctx_sp->ReadRegister (xmm1_info, xmm1_value);
DataExtractor rax_data, rdx_data, xmm0_data, xmm1_data;
rax_value.GetData(rax_data);
rdx_value.GetData(rdx_data);
xmm0_value.GetData(xmm0_data);
xmm1_value.GetData(xmm1_data);
uint32_t fp_bytes = 0; // Tracks how much of the xmm registers we've consumed so far
uint32_t integer_bytes = 0; // Tracks how much of the rax/rds registers we've consumed so far
uint32_t num_children = ClangASTContext::GetNumFields (ast_context, ret_value_type);
// Since we are in the small struct regime, assume we are not in memory.
is_memory = false;
for (uint32_t idx = 0; idx < num_children; idx++)
{
std::string name;
uint32_t field_bit_offset;
bool is_signed;
bool is_complex;
uint32_t count;
clang_type_t field_clang_type = ClangASTContext::GetFieldAtIndex (ast_context, ret_value_type, idx, name, &field_bit_offset);
size_t field_bit_width = ClangASTType::GetClangTypeBitWidth(ast_context, field_clang_type);
// If there are any unaligned fields, this is stored in memory.
if (field_bit_offset % field_bit_width != 0)
{
is_memory = true;
break;
}
uint32_t field_byte_width = field_bit_width/8;
uint32_t field_byte_offset = field_bit_offset/8;
DataExtractor *copy_from_extractor = NULL;
uint32_t copy_from_offset = 0;
if (ClangASTContext::IsIntegerType (field_clang_type, is_signed) || ClangASTContext::IsPointerType (field_clang_type))
{
if (integer_bytes < 8)
{
if (integer_bytes + field_byte_width <= 8)
{
// This is in RAX, copy from register to our result structure:
copy_from_extractor = &rax_data;
copy_from_offset = integer_bytes;
integer_bytes += field_byte_width;
}
else
{
// The next field wouldn't fit in the remaining space, so we pushed it to rdx.
copy_from_extractor = &rdx_data;
copy_from_offset = 0;
integer_bytes = 8 + field_byte_width;
}
}
else if (integer_bytes + field_byte_width <= 16)
{
copy_from_extractor = &rdx_data;
copy_from_offset = integer_bytes - 8;
integer_bytes += field_byte_width;
}
else
{
// The last field didn't fit. I can't see how that would happen w/o the overall size being
// greater than 16 bytes. For now, return a NULL return value object.
return return_valobj_sp;
}
}
else if (ClangASTContext::IsFloatingPointType (field_clang_type, count, is_complex))
{
// Structs with long doubles are always passed in memory.
if (field_bit_width == 128)
{
is_memory = true;
break;
}
else if (field_bit_width == 64)
{
// These have to be in a single xmm register.
if (fp_bytes == 0)
copy_from_extractor = &xmm0_data;
else
copy_from_extractor = &xmm1_data;
copy_from_offset = 0;
fp_bytes += field_byte_width;
}
else if (field_bit_width == 32)
{
// This one is kind of complicated. If we are in an "eightbyte" with another float, we'll
// be stuffed into an xmm register with it. If we are in an "eightbyte" with one or more ints,
// then we will be stuffed into the appropriate GPR with them.
bool in_gpr;
if (field_byte_offset % 8 == 0)
{
// We are at the beginning of one of the eightbytes, so check the next element (if any)
if (idx == num_children - 1)
in_gpr = true;
else
{
uint32_t next_field_bit_offset;
clang_type_t next_field_clang_type = ClangASTContext::GetFieldAtIndex (ast_context,
ret_value_type,
idx + 1,
name,
&next_field_bit_offset);
if (ClangASTContext::IsIntegerType (next_field_clang_type, is_signed))
in_gpr = true;
else
{
copy_from_offset = 0;
in_gpr = false;
}
}
}
else if (field_byte_offset % 4 == 0)
{
// We are inside of an eightbyte, so see if the field before us is floating point:
// This could happen if somebody put padding in the structure.
if (idx == 0)
in_gpr = false;
else
{
uint32_t prev_field_bit_offset;
clang_type_t prev_field_clang_type = ClangASTContext::GetFieldAtIndex (ast_context,
ret_value_type,
idx - 1,
name,
&prev_field_bit_offset);
if (ClangASTContext::IsIntegerType (prev_field_clang_type, is_signed))
in_gpr = true;
else
{
copy_from_offset = 4;
in_gpr = false;
}
}
}
else
{
is_memory = true;
continue;
}
// Okay, we've figured out whether we are in GPR or XMM, now figure out which one.
if (in_gpr)
{
if (integer_bytes < 8)
{
// This is in RAX, copy from register to our result structure:
copy_from_extractor = &rax_data;
copy_from_offset = integer_bytes;
integer_bytes += field_byte_width;
}
else
{
copy_from_extractor = &rdx_data;
copy_from_offset = integer_bytes - 8;
integer_bytes += field_byte_width;
}
}
else
{
if (fp_bytes < 8)
copy_from_extractor = &xmm0_data;
else
copy_from_extractor = &xmm1_data;
fp_bytes += field_byte_width;
}
}
}
if (!copy_from_extractor)
return return_valobj_sp;
copy_from_extractor->CopyByteOrderedData (copy_from_offset,
field_byte_width,
data_sp->GetBytes() + field_byte_offset,
field_byte_width,
target_byte_order);
}
if (!is_memory)
{
// The result is in our data buffer. Let's make a variable object out of it:
return_valobj_sp = ValueObjectConstResult::Create (&thread,
ast_context,
ret_value_type,
ConstString(""),
return_ext);
}
}
if (is_memory)
{
unsigned rax_id = reg_ctx_sp->GetRegisterInfoByName("rax", 0)->kinds[eRegisterKindLLDB];
lldb::addr_t storage_addr = (uint64_t)thread.GetRegisterContext()->ReadRegisterAsUnsigned(rax_id, 0);
return_valobj_sp = ValueObjectMemory::Create (&thread,
"",
Address (storage_addr, NULL),
ast_type);
}
}
return return_valobj_sp;
}
bool

View File

@ -45,10 +45,15 @@ public:
GetArgumentValues (lldb_private::Thread &thread,
lldb_private::ValueList &values) const;
virtual bool
GetReturnValue (lldb_private::Thread &thread,
lldb_private::Value &value) const;
protected:
lldb::ValueObjectSP
GetReturnValueObjectSimple (lldb_private::Thread &thread,
lldb_private::ClangASTType &ast_type) const;
public:
virtual lldb::ValueObjectSP
GetReturnValueObjectImpl (lldb_private::Thread &thread,
lldb_private::ClangASTType &type) const;
virtual bool
CreateFunctionEntryUnwindPlan (lldb_private::UnwindPlan &unwind_plan);

View File

@ -9,7 +9,7 @@
#include "InferiorCallPOSIX.h"
#include "lldb/Core/StreamFile.h"
#include "lldb/Core/Value.h"
#include "lldb/Core/ValueObject.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
@ -70,9 +70,12 @@ bool lldb_private::InferiorCallMmap(Process *process, addr_t &allocated_addr,
AddressRange mmap_range;
if (sc.GetAddressRange(range_scope, 0, use_inline_block_range, mmap_range))
{
ClangASTContext *clang_ast_context = process->GetTarget().GetScratchClangASTContext();
lldb::clang_type_t clang_void_ptr_type = clang_ast_context->GetVoidPtrType(false);
ThreadPlanCallFunction *call_function_thread_plan
= new ThreadPlanCallFunction (*thread,
mmap_range.GetBaseAddress(),
ClangASTType (clang_ast_context->getASTContext(), clang_void_ptr_type),
stop_other_threads,
discard_on_error,
&addr,
@ -84,13 +87,6 @@ bool lldb_private::InferiorCallMmap(Process *process, addr_t &allocated_addr,
lldb::ThreadPlanSP call_plan_sp (call_function_thread_plan);
if (call_plan_sp)
{
ValueSP return_value_sp (new Value);
ClangASTContext *clang_ast_context = process->GetTarget().GetScratchClangASTContext();
lldb::clang_type_t clang_void_ptr_type = clang_ast_context->GetVoidPtrType(false);
return_value_sp->SetValueType (Value::eValueTypeScalar);
return_value_sp->SetContext (Value::eContextTypeClangType, clang_void_ptr_type);
call_function_thread_plan->RequestReturnValue (return_value_sp);
StreamFile error_strm;
StackFrame *frame = thread->GetStackFrameAtIndex (0).get();
if (frame)
@ -106,7 +102,8 @@ bool lldb_private::InferiorCallMmap(Process *process, addr_t &allocated_addr,
error_strm);
if (result == eExecutionCompleted)
{
allocated_addr = return_value_sp->GetScalar().ULongLong();
allocated_addr = call_plan_sp->GetReturnValueObject()->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
if (process->GetAddressByteSize() == 4)
{
if (allocated_addr == UINT32_MAX)
@ -155,6 +152,7 @@ bool lldb_private::InferiorCallMunmap(Process *process, addr_t addr,
{
lldb::ThreadPlanSP call_plan_sp (new ThreadPlanCallFunction (*thread,
munmap_range.GetBaseAddress(),
ClangASTType(),
stop_other_threads,
discard_on_error,
&addr,

View File

@ -5411,9 +5411,10 @@ ClangASTContext::IsIntegerType (clang_type_t clang_type, bool &is_signed)
if (builtin_type)
{
if (builtin_type->isInteger())
{
is_signed = builtin_type->isSignedInteger();
return true;
return true;
}
}
return false;

View File

@ -12,6 +12,7 @@
#include "lldb/Core/Value.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/Symbol/ClangASTType.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
using namespace lldb;
@ -104,25 +105,69 @@ ABI::GetRegisterInfoByKind (RegisterKind reg_kind, uint32_t reg_num, RegisterInf
ValueObjectSP
ABI::GetReturnValueObject (Thread &thread,
ClangASTType &ast_type) const
ClangASTType &ast_type,
bool persistent) const
{
if (!ast_type.IsValid())
return ValueObjectSP();
Value ret_value;
ret_value.SetContext(Value::eContextTypeClangType,
ast_type.GetOpaqueQualType());
if (GetReturnValue (thread, ret_value))
ValueObjectSP return_valobj_sp;
return_valobj_sp = GetReturnValueObjectImpl(thread, ast_type);
if (!return_valobj_sp)
return return_valobj_sp;
// Now turn this into a persistent variable.
// FIXME: This code is duplicated from Target::EvaluateExpression, and it is used in similar form in a couple
// of other places. Figure out the correct Create function to do all this work.
if (persistent)
{
return ValueObjectConstResult::Create(
thread.GetStackFrameAtIndex(0).get(),
ast_type.GetASTContext(),
ret_value,
ConstString("FunctionReturn"));
ClangPersistentVariables& persistent_variables = thread.GetProcess().GetTarget().GetPersistentVariables();
ConstString persistent_variable_name (persistent_variables.GetNextPersistentVariableName());
lldb::ValueObjectSP const_valobj_sp;
// Check in case our value is already a constant value
if (return_valobj_sp->GetIsConstant())
{
const_valobj_sp = return_valobj_sp;
const_valobj_sp->SetName (persistent_variable_name);
}
else
const_valobj_sp = return_valobj_sp->CreateConstantValue (persistent_variable_name);
lldb::ValueObjectSP live_valobj_sp = return_valobj_sp;
return_valobj_sp = const_valobj_sp;
ClangExpressionVariableSP clang_expr_variable_sp(persistent_variables.CreatePersistentVariable(return_valobj_sp));
assert (clang_expr_variable_sp.get());
// Set flags and live data as appropriate
const Value &result_value = live_valobj_sp->GetValue();
switch (result_value.GetValueType())
{
case Value::eValueTypeHostAddress:
case Value::eValueTypeFileAddress:
// we don't do anything with these for now
break;
case Value::eValueTypeScalar:
clang_expr_variable_sp->m_flags |= ClangExpressionVariable::EVIsLLDBAllocated;
clang_expr_variable_sp->m_flags |= ClangExpressionVariable::EVNeedsAllocation;
break;
case Value::eValueTypeLoadAddress:
clang_expr_variable_sp->m_live_sp = live_valobj_sp;
clang_expr_variable_sp->m_flags |= ClangExpressionVariable::EVIsProgramReference;
break;
}
return_valobj_sp = clang_expr_variable_sp->GetValueObject();
}
else
return ValueObjectSP();
return return_valobj_sp;
}

View File

@ -861,7 +861,7 @@ Thread::QueueThreadPlanForCallFunction (bool abort_other_plans,
bool stop_other_threads,
bool discard_on_error)
{
ThreadPlanSP thread_plan_sp (new ThreadPlanCallFunction (*this, function, arg, stop_other_threads, discard_on_error));
ThreadPlanSP thread_plan_sp (new ThreadPlanCallFunction (*this, function, ClangASTType(), arg, stop_other_threads, discard_on_error));
QueueThreadPlan (thread_plan_sp, abort_other_plans);
return thread_plan_sp.get();
}

View File

@ -37,6 +37,7 @@ using namespace lldb_private;
ThreadPlanCallFunction::ThreadPlanCallFunction (Thread &thread,
Address &function,
const ClangASTType &return_type,
addr_t arg,
bool stop_other_threads,
bool discard_on_error,
@ -47,6 +48,7 @@ ThreadPlanCallFunction::ThreadPlanCallFunction (Thread &thread,
m_stop_other_threads (stop_other_threads),
m_function_addr (function),
m_function_sp (NULL),
m_return_type (return_type),
m_takedown_done (false),
m_stop_address (LLDB_INVALID_ADDRESS)
{
@ -149,6 +151,7 @@ ThreadPlanCallFunction::ThreadPlanCallFunction (Thread &thread,
ThreadPlanCallFunction::ThreadPlanCallFunction (Thread &thread,
Address &function,
const ClangASTType &return_type,
bool stop_other_threads,
bool discard_on_error,
addr_t *arg1_ptr,
@ -162,6 +165,7 @@ ThreadPlanCallFunction::ThreadPlanCallFunction (Thread &thread,
m_stop_other_threads (stop_other_threads),
m_function_addr (function),
m_function_sp(NULL),
m_return_type (return_type),
m_takedown_done (false)
{
SetOkayToDiscard (discard_on_error);
@ -281,13 +285,12 @@ ThreadPlanCallFunction::DoTakedown ()
LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP));
if (!m_takedown_done)
{
// TODO: how do we tell if all went well?
if (m_return_value_sp)
const ABI *abi = m_thread.GetProcess().GetABI().get();
if (abi && m_return_type.IsValid())
{
const ABI *abi = m_thread.GetProcess().GetABI().get();
if (abi)
abi->GetReturnValue(m_thread, *m_return_value_sp);
m_return_valobj_sp = abi->GetReturnValueObject (m_thread, m_return_type);
}
if (log)
log->Printf ("DoTakedown called for thread 0x%4.4llx, m_valid: %d complete: %d.\n", m_thread.GetID(), m_valid, IsPlanComplete());
m_takedown_done = true;

View File

@ -44,7 +44,7 @@ ThreadPlanCallUserExpression::ThreadPlanCallUserExpression (Thread &thread,
lldb::addr_t *this_arg,
lldb::addr_t *cmd_arg,
ClangUserExpression::ClangUserExpressionSP &user_expression_sp) :
ThreadPlanCallFunction (thread, function, arg, stop_other_threads, discard_on_error, this_arg, cmd_arg),
ThreadPlanCallFunction (thread, function, ClangASTType(), arg, stop_other_threads, discard_on_error, this_arg, cmd_arg),
m_user_expression_sp (user_expression_sp)
{
}

View File

@ -0,0 +1,5 @@
LEVEL = ../../make
C_SOURCES := call-func.c
include $(LEVEL)/Makefile.rules

View File

@ -0,0 +1,127 @@
"""
Test getting return-values correctly when stepping out
"""
import os, time
import re
import unittest2
import lldb, lldbutil
from lldbtest import *
class ReturnValueTestCase(TestBase):
mydir = os.path.join("functionalities", "return-value")
@unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin")
@python_api_test
def test_with_dsym_python(self):
"""Test getting return values from stepping out with dsyms."""
self.buildDsym()
self.do_return_value()
@python_api_test
def test_with_dwarf_python(self):
"""Test getting return values from stepping out."""
self.buildDwarf()
self.do_return_value()
def do_return_value(self):
"""Test getting return values from stepping out."""
exe = os.path.join(os.getcwd(), "a.out")
error = lldb.SBError()
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)
inner_sint_bkpt = target.BreakpointCreateByName("inner_sint", exe)
self.assertTrue(inner_sint_bkpt, VALID_BREAKPOINT)
# Now launch the process, and do not stop at entry point.
process = target.LaunchSimple(None, None, os.getcwd())
self.assertTrue(process, PROCESS_IS_VALID)
# The stop reason of the thread should be breakpoint.
self.assertTrue(process.GetState() == lldb.eStateStopped,
STOPPED_DUE_TO_BREAKPOINT)
# Now finish, and make sure the return value is correct.
thread = lldbutil.get_stopped_thread (process, lldb.eStopReasonBreakpoint)
thread.StepOut();
self.assertTrue (process.GetState() == lldb.eStateStopped)
self.assertTrue (thread.GetStopReason() == lldb.eStopReasonPlanComplete)
frame = thread.GetFrameAtIndex(0)
fun_name = frame.GetFunctionName()
self.assertTrue (fun_name == "outer_sint")
return_value = thread.GetStopReturnValue()
self.assertTrue (return_value.IsValid())
in_int = frame.FindVariable ("value").GetValueAsSigned(error)
self.assertTrue (error.Success())
ret_int = return_value.GetValueAsSigned(error)
self.assertTrue (error.Success())
self.assertTrue (in_int == ret_int)
# Run again and we will stop in inner_sint the second time outer_sint is called.
#Then test stepping out two frames at once:
process.Continue()
thread_list = lldbutil.get_threads_stopped_at_breakpoint (process, inner_sint_bkpt)
self.assertTrue(len(thread_list) == 1)
thread = thread_list[0]
frame = thread.GetFrameAtIndex(1)
fun_name = frame.GetFunctionName ()
self.assertTrue (fun_name == "outer_sint")
in_int = frame.FindVariable ("value").GetValueAsSigned(error)
self.assertTrue (error.Success())
thread.StepOutOfFrame (frame)
self.assertTrue (process.GetState() == lldb.eStateStopped)
self.assertTrue (thread.GetStopReason() == lldb.eStopReasonPlanComplete)
frame = thread.GetFrameAtIndex(0)
fun_name = frame.GetFunctionName()
self.assertTrue (fun_name == "main")
ret_value = thread.GetStopReturnValue()
self.assertTrue (return_value.IsValid())
ret_int = ret_value.GetValueAsSigned (error)
self.assertTrue (error.Success())
self.assertTrue (in_int == ret_int)
# Now try some simple returns that have different types:
inner_float_bkpt = target.BreakpointCreateByName("inner_float", exe)
self.assertTrue(inner_float_bkpt, VALID_BREAKPOINT)
process.Continue()
thread_list = lldbutil.get_threads_stopped_at_breakpoint (process, inner_float_bkpt)
self.assertTrue (len(thread_list) == 1)
thread = thread_list[0]
frame = thread.GetFrameAtIndex(0)
in_value = frame.FindVariable ("value")
in_float = float (in_value.GetValue())
thread.StepOut()
self.assertTrue (process.GetState() == lldb.eStateStopped)
self.assertTrue (thread.GetStopReason() == lldb.eStopReasonPlanComplete)
frame = thread.GetFrameAtIndex(0)
fun_name = frame.GetFunctionName()
self.assertTrue (fun_name == "outer_float")
return_value = thread.GetStopReturnValue()
self.assertTrue (return_value.IsValid())
return_float = float (return_value.GetValue())
self.assertTrue(in_float == return_float)
if __name__ == '__main__':
import atexit
lldb.SBDebugger.Initialize()
atexit.register(lambda: lldb.SBDebugger.Terminate())
unittest2.main()

View File

@ -0,0 +1,90 @@
int
inner_sint (int value)
{
return value;
}
int
outer_sint (int value)
{
return inner_sint (value);
}
float
inner_float (float value)
{
return value;
}
float
outer_float (float value)
{
return inner_float(value);
}
double
inner_double (double value)
{
return value;
}
double
outer_double (double value)
{
return inner_double(value);
}
long double
inner_long_double (long double value)
{
return value;
}
long double
outer_long_double (long double value)
{
return inner_long_double(value);
}
struct
large_return_struct
{
long long first_long;
long long second_long;
long long third_long;
long long fourth_long;
};
struct large_return_struct
return_large_struct (long long first, long long second, long long third, long long fourth)
{
return (struct large_return_struct) {first, second, third, fourth};
}
int
main ()
{
int first_int = 123456;
int second_int = 234567;
outer_sint (first_int);
outer_sint (second_int);
float float_value = 12.34;
outer_float (float_value);
double double_value = -23.45;
outer_double (double_value);
long double long_double_value = -3456789.987654321;
outer_long_double (long_double_value);
return_large_struct (10, 20, 30, 40);
return 0;
}