blob: bbcb835971a2e1a37dc5fb7bd69a2660438c2b5c [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef GAPIR_STACK_H
#define GAPIR_STACK_H
#include "base_type.h"
#include "memory_manager.h"
#include <gapic/log.h>
#include <gapic/static_array.h>
#include <stdint.h>
#include <string.h>
#include <vector>
namespace gapir {
// Strongly typed, limited size stack for the stack based virtual machine. If an invalid operation
// is called on the stack then the stack will go into an invalid state where each operation is no op
// (except print stack). From an invalid state the stack can't go back to a valid state again.
class Stack {
public:
// Representation of an unconverted value from the stack.
typedef uint64_t BaseValue;
// Construct a new stack with the given size and memory manager
// The memory manager is needed to resolve constant and volatile pointers to absolute pointers
Stack(uint32_t size, const MemoryManager* memoryManager);
// Pop the element from the top of the stack as the given type if its type is matching with the
// given type. For pointer types convert the pointer to an absolute pointer before returning it.
// Puts the stack into an invalid state if called on an empty stack or if the requested type
// doesn't match with the type of the value at the top of the stack.
template <typename T>
T pop() {
if (!popCheck("pop")) {
return T();
}
GAPID_DEBUG("-%s pop()", mStack[mTop-1].debugInfo(mMemoryManager));
return PopImpl<T>::pop(this);
}
// Pop the static array (stored as a pointer) from the top of the stack.
template <typename T, int N>
gapic::StaticArray<T, N> pop() {
T* ptr = pop<T*>();
gapic::StaticArray<T, N> out;
for (int i = 0; i < N; i++) {
out[i] = ptr[i];
}
return out;
}
// Pop the volatile pointer from the top of the stack. If the top element
// is not a volatile pointer puts the stack into and invalid state. Also,
// puts the stack into an invalid state if called on an empty stack.
// Use with care, this template casts a pointer to a T* with no way of
// knowing if that is safe.
template <typename T>
T* popVolatile() {
if (!popCheck("popVolatile")) {
return nullptr;
}
BaseType type = getTopType();
if (type != BaseType::VolatilePointer) {
GAPID_WARNING("popVolatile called for type %s", baseTypeName(type));
mValid = false;
return nullptr;
}
return pop<T*>();
}
// Pop the constant pointer from the top of the stack. If the top element
// is not a constant pointer puts the stack into and invalid state. Also,
// puts the stack into an invalid state if called on an empty stack.
// Use with care, this template casts a pointer to a const T* with no way of
// knowing if that is safe.
template <typename T>
const T* popConstant() {
if (!popCheck("popConstant")) {
return nullptr;
}
BaseType type = getTopType();
if (type != BaseType::ConstantPointer) {
GAPID_WARNING("popConstant called for type %s", baseTypeName(type));
mValid = false;
return nullptr;
}
return pop<const T*>();
}
// Pop the element from the top of the stack and return its unconverted typed value.
// Puts the stack into an invalid state if called on an empty stack.
BaseValue popBaseValue() {
if (!popCheck("popBaseValue")) {
return 0;
}
mTop--;
return mStack[mTop].getBaseValue();
}
// Push the given type and base value to the top of the stack. Put the
// stack into invalid state if called on a full stack.
void pushValue(BaseType type, BaseValue value) {
if (!pushCheck("pushValue")) {
return;
}
pushFrom(type, &value);
checkTopForInvalidPointer("pushValue");
}
// Push the given value to the top of the stack with a type determined by the type of the value
// given. Put the stack into invalid state if called on a full stack.
template <typename T>
void push(T value) {
if (!pushCheck("push")) {
return;
}
mStack[mTop].set(value);
if (!checkTopForInvalidPointer("push")) {
return;
}
GAPID_DEBUG("+%s push()", mStack[mTop].debugInfo(mMemoryManager));
mTop++;
}
// Returns the type of the element at the top of the stack. Put the stack into invalid state if
// called on an empty stack.
BaseType getTopType();
// Discards count amount of elements from the top of the stack. Put the stack into invalid state
// if the stack contains fewer element then the given count.
void discard(uint32_t count);
// Clone the n-th element from the top of the stack to the top of the stack. The currently top
// most element is the 0th and the index is increasing with going down in the stack. Put the
// stack into invalid state if called on a full stacked or if the given index points out from
// the stack (greater then current size minus one).
void clone(uint32_t n);
// Print the content of the stack to the log output. The output is only written to the log
// output if the debug level is at least DEBUG. This function will work even if the stack is not
// in a valid state.
void printStack() const;
// Returns if the stack is in a valid state or not.
bool isValid() const {
return mValid;
}
// Pop the item from the top of the stack to the given memory address. The number of bytes
// written to the address is determined by the type of the element at the top of the stack.
// Pointers are converted to absolute pointers before writing to the address.
// The stack will enter an invalid state if called on an empty stack.
// Take care if using an address on the program stack. Use getTopType()
// to check the type of the object you will pop and/or
// make sure the receiver is sizeof(BaseValue).
void popTo(void* address);
// Push a new item to the stack with the given type from the given memory address. Put the stack
// into invalid state if it is already full before the call.
void pushFrom(BaseType type, const void* data);
private:
// Check that the stack is valid and a pop is allowed (non-empty).
bool popCheck(const char * what);
// Check that the stack is valid and a push is allowed (non-full).
bool pushCheck(const char * what);
// Check that if top is a pointer type that it is valid.
bool checkTopForInvalidPointer(const char *what);
// Check that top is a pointer type, that it is valid and return it.
const void* checkAndGetTopPointer(const char *what);
// Implementation of the pop method for non pointer types.
template <typename T>
struct PopImpl {
static T pop(Stack* stack) {
stack->mTop--;
BaseType baseType = TypeToBaseType<typename std::remove_cv<T>::type>::type;
if (stack->mStack[stack->mTop].type() != baseType) {
stack->mValid = false;
GAPID_WARNING(
"Pop type (%s) doesn't match with the type at the top of the stack (%s)",
baseTypeName(baseType),
baseTypeName(stack->mStack[stack->mTop].type()));
return T();
}
return stack->mStack[stack->mTop].value<T>();
}
};
// Implementation of the pop method for pointer types.
// Use with care, this template casts a pointer to a T* with no
// way of knowing if that is safe.
template <typename T>
struct PopImpl<T*> {
static T* pop(Stack* stack) {
stack->mTop--;
const void* pointer = stack->checkAndGetTopPointer("pop");
return const_cast<T*>(static_cast<const T*>(pointer));
}
};
class Entry {
public:
const void* valuePtr() const {
return &mValue;
}
template <typename T>
const T value() const {
static_assert(sizeof(mValue) >= sizeof(T),
"T is too large to be used as value");
T t;
if (!getTo(&t)) {
GAPID_WARNING(
"Error: read stack value inappropriate type %s wanted %s",
baseTypeName(mType), baseTypeName(TypeToBaseType<T>::type));
return T();
}
return t;
}
const BaseType& type() const {
return mType;
}
void set(bool b) {
mType = TypeToBaseType<bool>::type;
mValue.b = b;
}
void set(int8_t i8) {
mType = TypeToBaseType<int8_t>::type;
mValue.i8 = i8;
}
void set(int16_t i16) {
mType = TypeToBaseType<int16_t>::type;
mValue.i16 = i16;
}
void set(int32_t i32) {
mType = TypeToBaseType<int32_t>::type;
mValue.i32 = i32;
}
void set(int64_t i64) {
mType = TypeToBaseType<int64_t>::type;
mValue.i64 = i64;
}
void set(uint8_t u8) {
mType = TypeToBaseType<uint8_t>::type;
mValue.u8 = u8;
}
void set(uint16_t u16) {
mType = TypeToBaseType<uint16_t>::type;
mValue.u16 = u16;
}
void set(uint32_t u32) {
mType = TypeToBaseType<uint32_t>::type;
mValue.u32 = u32;
}
void set(uint64_t u64) {
mType = TypeToBaseType<uint64_t>::type;
mValue.u64 = u64;
}
void set(float f) {
mType = TypeToBaseType<float>::type;
mValue.f = f;
}
void set(double d) {
mType = TypeToBaseType<double>::type;
mValue.d = d;
}
template <typename T>
void set(T* p) {
mType = BaseType::AbsolutePointer;
mValue.p = p;
}
template <typename T>
void set(T val) {
static_assert(sizeof(T) == sizeof(mValue.u32),
"Enum base type is not uint32_t");
mType = BaseType::Uint32;
mValue.u32 = uint32_t(val);
}
void set(BaseType type, const void* data) {
// Little endian assumption
memcpy(&mValue, data, baseTypeSize(type));
mType = type;
}
bool getTo(bool* b) const {
if (mType != TypeToBaseType<bool>::type) {
return false;
}
*b = mValue.b;
return true;
}
bool getTo(int8_t* i8) const {
if (mType != TypeToBaseType<int8_t>::type) {
return false;
}
*i8 = mValue.i8;
return true;
}
bool getTo(int16_t* i16) const {
if (mType != TypeToBaseType<int16_t>::type) {
return false;
}
*i16 = mValue.i16;
return true;
}
bool getTo(int32_t* i32) const {
if (mType != TypeToBaseType<int32_t>::type) {
return false;
}
*i32 = mValue.i32;
return true;
}
bool getTo(int64_t* i64) const {
if (mType != TypeToBaseType<int64_t>::type) {
return false;
}
*i64 = mValue.i64;
return true;
}
bool getTo(uint8_t* u8) const {
if (mType != TypeToBaseType<uint8_t>::type) {
return false;
}
*u8 = mValue.u8;
return true;
}
bool getTo(uint16_t* u16) const {
if (mType != TypeToBaseType<uint16_t>::type) {
return false;
}
*u16 = mValue.u16;
return true;
}
bool getTo(uint32_t* u32) const {
if (mType != TypeToBaseType<uint32_t>::type &&
mType != BaseType::VolatilePointer &&
mType != BaseType::ConstantPointer) {
return false;
}
*u32 = mValue.u32;
return true;
}
bool getTo(uint64_t* u64) const {
if (mType != TypeToBaseType<uint64_t>::type) {
return false;
}
*u64 = mValue.u64;
return true;
}
bool getTo(float* f) const {
if (mType != TypeToBaseType<float>::type) {
return false;
}
*f = mValue.f;
return true;
}
bool getTo(double* d) const {
if (mType != TypeToBaseType<double>::type) {
return false;
}
*d = mValue.d;
return true;
}
template <typename T>
bool getTo(T* val) const {
static_assert(sizeof(T) == sizeof(mValue.u32),
"Enum base type is not uint32_t");
if (mType != BaseType::Uint32) {
return false;
}
*val = T(mValue.u32);
return true;
}
bool getTo(const void** p) const {
if (mType != BaseType::AbsolutePointer) {
return false;
}
*p = mValue.p;
return true;
}
bool getTo(void** p) const {
if (mType != BaseType::AbsolutePointer) {
return false;
}
*p = mValue.p;
return true;
}
BaseValue getBaseValue() const {
return mValue.bv;
}
// Return a string describing the stack entry.
// The pointer returned is only valid until the next call to debugInfo, regardless of the
// Entry instance.
// This function is not thread safe.
const char* debugInfo(const MemoryManager* memoryManager) const;
private:
// Type of the element stored by this entry
BaseType mType;
// Union of all possible types stored on the stack for creating a unified value type with
// getter function to access the value as a specific type
union ValueType {
bool b;
int8_t i8;
int16_t i16;
int32_t i32;
int64_t i64;
uint8_t u8;
uint16_t u16;
uint32_t u32;
uint64_t u64;
float f;
double d;
void* p;
BaseValue bv;
};
ValueType mValue;
static_assert(sizeof(BaseValue) >= sizeof(ValueType),
"Stack::BaseValue is not large enough");
};
// Indicates if the stack is in a consistent state (true value) or not (false value). The stack
// go into an inconsistent state after an invalid operation. When mValid is false then all of
// the operation on the stack (expect printing the stack) produce a warning message and falls
// back to no op (with zero initialized return value where necessary). The stack can't go back
// from an invalid state to a valid state again.
bool mValid;
// Contains the offset of the first empty slot in the stack from the bottom of the stack. The
// value of it indicates the number of elements currently in the stack.
uint32_t mTop;
// mStack stores the entries currently in the stack. The 0th element corresponds to the bottom
// of the stack.
std::vector<Entry> mStack;
// Reference to the memory manager used to resolve constant and volatile pointers to absolute
// pointers when thy are popped from the stack.
const MemoryManager* mMemoryManager;
};
} // namespace gapir
#endif // GAPIR_STACK_H