blob: b2444d5f2828a46c44a475902bdfa131362dcaa7 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_DEVELOPER_SHELL_INTERPRETER_SRC_CODE_H_
#define SRC_DEVELOPER_SHELL_INTERPRETER_SRC_CODE_H_
#include <memory>
#include <set>
#include <vector>
#include "src/developer/shell/interpreter/src/nodes.h"
#include "src/developer/shell/interpreter/src/value.h"
namespace shell {
namespace interpreter {
namespace code {
// Defines all the operations the interpreter can execute.
enum class Opcode : uint64_t {
// Nop: do nothing.
kNop,
// Pops a value from the stack and emits it (send it back to the client).
kEmitResult,
// Pops two 8 bit integers from the stack, adds them and pushes the result to the stack.
kInt8Addition,
// Pops two 16 bit integers from the stack, adds them and pushes the result to the stack.
kInt16Addition,
// Pops two 32 bit integers from the stack, adds them and pushes the result to the stack.
kInt32Addition,
// Pops two 64 bit integers from the stack, adds them and pushes the result to the stack.
kInt64Addition,
// Pushes a 64 bit literal to the thread's value stack.
kLiteral64,
// Loads a 8 bit global variable and pushes it to the stack.
kLoadRaw8,
// Loads a 16 bit global variable and pushes it to the stack.
kLoadRaw16,
// Loads a 32 bit global variable and pushes it to the stack.
kLoadRaw32,
// Loads a 64 bit global variable and pushes it to the stack.
kLoadRaw64,
// Loads a referenced counted value from a global variable and pushes it to the thread's value
// stack.
kLoadReferenceCounted,
// Initializes an object.
// Value stack before:
// <reference to uninitialized object>
// <initial value N>
// <initial value N-1>
// ...
// (where 1, 2, ..., N are values in the order they appear in the schema of the object)
// Value stack after:
// <reference to initialized object>
kObjectInit,
// Allocates an object and sets its schema.
// Value stack before:
// ...
// Value stack after:
// <reference>
// ...
kObjectNew,
// Pushes a reference counted literal to the thread's value stack. Increment the reference count
// for the object.
kReferenceCountedLiteral,
// Return from code execution. The execution goes back to the calling scope or stops if it was
// the last execution scope.
kRet,
// Pops two 8 bit signed integers from the stack, adds them and pushes the result to the stack.
// If an overflow or and underflow occur, an error is generated and the execution stops.
kSint8AdditionWithExceptions,
// Pops two 16 bit signed integers from the stack, adds them and pushes the result to the stack.
// If an overflow or and underflow occur, an error is generated and the execution stops.
kSint16AdditionWithExceptions,
// Pops two 32 bit signed integers from the stack, adds them and pushes the result to the stack.
// If an overflow or and underflow occur, an error is generated and the execution stops.
kSint32AdditionWithExceptions,
// Pops two 64 bit signed integers from the stack, adds them and pushes the result to the stack.
// If an overflow or and underflow occur, an error is generated and the execution stops.
kSint64AdditionWithExceptions,
// Pops a value from the thread's value stack and stores it into a 8 bit global variable.
kStoreRaw8,
// Pops a value from the thread's value stack and stores it into a 16 bit global variable.
kStoreRaw16,
// Pops a value from the thread's value stack and stores it into a 32 bit global variable.
kStoreRaw32,
// Pops a value from the thread's value stack and stores it into a 64 bit global variable.
kStoreRaw64,
// Pops a value from the stack, releases the old value of the global variable and stores the new
// value.
kStoreReferenceCounted,
// Pops several strings from the stack, concatenates them and pushes the result to the stack.
kStringConcatenation,
// Pops two 8 bit unsigned integers from the stack, adds them and pushes the result to the stack.
// If an overflow occurs, an error is generated and the execution stops.
kUint8AdditionWithExceptions,
// Pops two 16 bit unsigned integers from the stack, adds them and pushes the result to the stack.
// If an overflow occurs, an error is generated and the execution stops.
kUint16AdditionWithExceptions,
// Pops two 32 bit unsigned integers from the stack, adds them and pushes the result to the stack.
// If an overflow occurs, an error is generated and the execution stops.
kUint32AdditionWithExceptions,
// Pops two 64 bit unsigned integers from the stack, adds them and pushes the result to the stack.
// If an overflow occurs, an error is generated and the execution stops.
kUint64AdditionWithExceptions,
};
// Defines some code. This can represent the code for one function or the code for the pending
// instructions of one execution context.
class Code {
public:
Code() = default;
const std::vector<uint64_t>& code() const { return code_; }
// Adds an emit result.
void EmitResult(std::unique_ptr<Type> type) {
emplace_back_opcode(Opcode::kEmitResult);
code_.emplace_back(reinterpret_cast<uint64_t>(type.get()));
types_.emplace_back(std::move(type));
}
// Adds an integer addition.
void IntegerAddition(bool with_exceptions, size_t size, bool is_signed) {
switch (size) {
case 1:
emplace_back_opcode(with_exceptions ? (is_signed ? Opcode::kSint8AdditionWithExceptions
: Opcode::kUint8AdditionWithExceptions)
: Opcode::kInt8Addition);
break;
case 2:
emplace_back_opcode(with_exceptions ? (is_signed ? Opcode::kSint16AdditionWithExceptions
: Opcode::kUint16AdditionWithExceptions)
: Opcode::kInt16Addition);
break;
case 4:
emplace_back_opcode(with_exceptions ? (is_signed ? Opcode::kSint32AdditionWithExceptions
: Opcode::kUint32AdditionWithExceptions)
: Opcode::kInt32Addition);
break;
case 8:
emplace_back_opcode(with_exceptions ? (is_signed ? Opcode::kSint64AdditionWithExceptions
: Opcode::kUint64AdditionWithExceptions)
: Opcode::kInt64Addition);
break;
default:
FX_LOGS(FATAL) << "Bad integer size " << size;
}
}
// Adds a 64 bit literal operation.
void Literal64(uint64_t value) {
emplace_back_opcode(Opcode::kLiteral64);
code_.emplace_back(value);
}
// Adds a 64 bit literal operation.
void LoadReferenceCounted(size_t index) {
emplace_back_opcode(Opcode::kLoadReferenceCounted);
code_.emplace_back(index);
}
// Adds a global variable load operation.
void LoadRaw(size_t index, size_t size) {
switch (size) {
case 1:
emplace_back_opcode(Opcode::kLoadRaw8);
break;
case 2:
emplace_back_opcode(Opcode::kLoadRaw16);
break;
case 4:
emplace_back_opcode(Opcode::kLoadRaw32);
break;
case 8:
emplace_back_opcode(Opcode::kLoadRaw64);
break;
default:
FX_LOGS(FATAL) << "Bad builtin size " << size;
}
code_.emplace_back(index);
}
// Adds a ret operation.
void Ret() { emplace_back_opcode(Opcode::kRet); }
// Adds a global variable store operation.
void StoreRaw(uint64_t index, uint64_t size) {
switch (size) {
case 1:
emplace_back_opcode(Opcode::kStoreRaw8);
break;
case 2:
emplace_back_opcode(Opcode::kStoreRaw16);
break;
case 4:
emplace_back_opcode(Opcode::kStoreRaw32);
break;
case 8:
emplace_back_opcode(Opcode::kStoreRaw64);
break;
default:
FX_LOGS(FATAL) << "Bad builtin size " << size;
}
code_.emplace_back(index);
}
// Adds a reference counted global variable store operation.
void StoreReferenceCounted(uint64_t index) {
emplace_back_opcode(Opcode::kStoreReferenceCounted);
code_.emplace_back(index);
}
// Adds a string concatenation operation.
void StringConcatenation(size_t string_count) {
emplace_back_opcode(Opcode::kStringConcatenation);
code_.emplace_back(string_count);
}
// Adds a string literal operation.
void StringLiteral(String* value) {
strings_.emplace_back(value);
emplace_back_opcode(Opcode::kReferenceCountedLiteral);
code_.emplace_back(reinterpret_cast<uint64_t>(value));
}
// Adds an operation that allocates an object and leaves a reference to it on the stack.
void ObjectPush(const std::shared_ptr<ObjectSchema>& object_schema) {
emplace_back_opcode(Opcode::kObjectNew);
auto ref = object_schemas_.insert(object_schema);
// We store the shared_ptr so that the object that later gets allocated can ref and deref it.
const std::shared_ptr<ObjectSchema>* ptr = &(*(ref.first));
code_.emplace_back(reinterpret_cast<uint64_t>(ptr));
}
// Adds an operation that initializes an object.
void ObjectInit() { emplace_back_opcode(Opcode::kObjectInit); }
private:
void emplace_back_opcode(Opcode opcode) { code_.emplace_back(static_cast<uint64_t>(opcode)); }
struct SchemaPtrLT {
constexpr bool operator()(const std::shared_ptr<ObjectSchema>& lhs,
const std::shared_ptr<ObjectSchema>& rhs) const {
return lhs.get() < rhs.get();
}
};
// Tracking for the schemas used as arguments by the code. Because schemas are managed by
// shared_ptrs, and code_ only stores opaque values.
std::set<std::shared_ptr<ObjectSchema>, SchemaPtrLT> object_schemas_;
// Contains the operations. It's a mix of opcodes and arguments for the operations.
std::vector<uint64_t> code_;
// Keeps alive the string literals in the code.
std::vector<StringContainer> strings_;
// Keeps alive the types in the code.
std::vector<std::unique_ptr<Type>> types_;
};
} // namespace code
} // namespace interpreter
} // namespace shell
#endif // SRC_DEVELOPER_SHELL_INTERPRETER_SRC_CODE_H_