87b0dbab2d
This change adds enough functionality to compile hello world from S-expressions in a contrived way. It fails to handle this input realistically in several ways. 1) It assumes the existence of dart:core::print 2) It handles all dart values as C strings 3) It assumes only very static calls can be made with no named arguments 4) If a stack overflow is detected it just traps. 5) I'm not sure how the current dart runtime works but the contrived runtime used here puts a contrived thread object in `gs`. This change adds a basic framework for implementing new instructions that should make it possible to implement new instructions with much smaller changes however. Change-Id: Ic167a29908a875bfc234c4e9bf5f4ac2ff52a3a2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/117222 Commit-Queue: Jake Ehrlich <jakehehrlich@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
358 lines
13 KiB
C++
358 lines
13 KiB
C++
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "dart.h"
|
|
|
|
#include "llvm/ADT/StringSwitch.h"
|
|
#include "vm/dart_api_state.h"
|
|
|
|
using namespace llvm;
|
|
|
|
Value* DartThreadObject::GetOffset(Type* type, intptr_t offset) const {
|
|
auto& ctx = bb_builder_.Context();
|
|
auto& builder = bb_builder_.Builder();
|
|
auto int64ty = IntegerType::getInt64Ty(ctx);
|
|
// TODO: This is only correct for x86_64. On x86 we need to use 257,
|
|
// and only arm targets an entirely different mechanism will be
|
|
// required since there isn't an unused register. On Arm we can
|
|
// probably still use the thread register as long as we're careful
|
|
// to set it back on thread boundaries.
|
|
// On x86_64 fs (257) is used for TLS but gs (256) is unused
|
|
// On x86 gs (256) is used for TLS but fs (257) is unused
|
|
// we use the unused segment so as to not conflict with TLS which
|
|
// allows linking against native code that uses TLS without needing
|
|
// to handle any kind of context switching.
|
|
constexpr unsigned kDartThreadPointerAddressSpace = 256;
|
|
auto* ptr_tls = PointerType::get(type, kDartThreadPointerAddressSpace);
|
|
auto* offset_value = ConstantInt::get(int64ty, offset);
|
|
auto* tls_value = builder.CreateIntToPtr(offset_value, ptr_tls);
|
|
return builder.CreateLoad(tls_value);
|
|
}
|
|
|
|
Value* DartThreadObject::StackLimit() const {
|
|
auto& ctx = bb_builder_.Context();
|
|
return GetOffset(IntegerType::getInt64Ty(ctx), kThreadStackLimitOffset);
|
|
}
|
|
|
|
Value* BasicBlockBuilder::GetValue(const DartValue* v) {
|
|
auto iter = values_.find(v);
|
|
if (iter == values_.end()) {
|
|
auto* out = v->Make(*this);
|
|
values_[v] = out;
|
|
return out;
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
DartInstruction::~DartInstruction() {}
|
|
|
|
static Error CreateError(const Twine& err) {
|
|
return make_error<StringError>(err, inconvertibleErrorCode());
|
|
}
|
|
|
|
Value* DartConstant::Make(BasicBlockBuilder& bb_builder) const {
|
|
auto& ctx = bb_builder.Context();
|
|
switch (type) {
|
|
case DartConstant::Type::String:
|
|
auto constant = ConstantDataArray::getString(ctx, str);
|
|
auto gv =
|
|
new GlobalVariable(bb_builder.Module(), constant->getType(), true,
|
|
GlobalVariable::ExternalLinkage, constant);
|
|
return ConstantExpr::getBitCast(gv, GetType(bb_builder));
|
|
}
|
|
}
|
|
|
|
Type* DartConstant::GetType(BasicBlockBuilder& bb_builder) const {
|
|
// TODO: Right now this returns a c-string type but that's not correct.
|
|
// We should move this to a generic object type (e.g. a tagged pointer).
|
|
auto& ctx = bb_builder.Context();
|
|
auto int8ty = IntegerType::getInt8Ty(ctx);
|
|
return PointerType::get(int8ty, 0);
|
|
}
|
|
|
|
void InstCheckStackOverflow::Build(BasicBlockBuilder& bb_builder) const {
|
|
auto& builder = bb_builder.Builder();
|
|
auto& ctx = bb_builder.Context();
|
|
auto& module = bb_builder.Module();
|
|
auto& thread_object = bb_builder.ThreadObject();
|
|
|
|
// TODO: This is only correct on 64-bit architectures.
|
|
auto int64ty = IntegerType::getInt64Ty(ctx);
|
|
|
|
// Get the stack pointer.
|
|
auto spi_type = Intrinsic::getType(ctx, Intrinsic::stacksave);
|
|
auto spi_func = Intrinsic::getDeclaration(&module, Intrinsic::stacksave);
|
|
auto sp_raw = builder.CreateCall(spi_type, spi_func);
|
|
auto sp = builder.CreatePtrToInt(sp_raw, int64ty);
|
|
|
|
// Get the stack limit from the thread pointer.
|
|
auto stack_limit = thread_object.StackLimit();
|
|
|
|
// Now compare the stack pointer and limit.
|
|
auto error_bb = bb_builder.AddBasicBlock();
|
|
auto cont_bb = bb_builder.AddBasicBlock();
|
|
auto cmp = builder.CreateICmpULT(sp, stack_limit);
|
|
builder.CreateCondBr(cmp, error_bb, cont_bb);
|
|
|
|
// Now build the error path.
|
|
// TODO: Don't just trap here. For now we just trap rather than
|
|
// handling the proper exceptional control flow here.
|
|
builder.SetInsertPoint(error_bb);
|
|
auto trap_type = Intrinsic::getType(ctx, Intrinsic::trap);
|
|
auto trap_func = Intrinsic::getDeclaration(&module, Intrinsic::trap);
|
|
builder.CreateCall(trap_type, trap_func);
|
|
builder.CreateBr(cont_bb);
|
|
|
|
// Now pretend as if this new block is just the end of the block we
|
|
// started with.
|
|
builder.SetInsertPoint(cont_bb);
|
|
}
|
|
|
|
Expected<std::unique_ptr<DartInstruction>> InstCheckStackOverflow::Construct(
|
|
dart::SExpList* inst,
|
|
DartBasicBlockBuilder& bb_builder) {
|
|
// inst = (CheckStackoverflow)
|
|
return llvm::make_unique<InstCheckStackOverflow>();
|
|
}
|
|
|
|
void InstPushArgument::Build(BasicBlockBuilder& bb_builder) const {
|
|
bb_builder.PushArgument(bb_builder.GetValue(arg_));
|
|
}
|
|
|
|
Expected<std::unique_ptr<DartInstruction>> InstPushArgument::Construct(
|
|
dart::SExpList* inst,
|
|
DartBasicBlockBuilder& bb_builder) {
|
|
// inst = (PushArgument <arg>)
|
|
if (inst->Length() != 2) {
|
|
return CreateError("PushArgument should have exactly 1 argument");
|
|
}
|
|
dart::SExpSymbol* arg = inst->At(1)->AsSymbol();
|
|
if (!arg) {
|
|
return CreateError("Expected PushArgument's argument to be a symbol");
|
|
}
|
|
const DartValue* dvalue = bb_builder.GetDef(arg->value());
|
|
if (!dvalue) {
|
|
return CreateError(Twine(arg->value()) + " is not a valid symbol");
|
|
}
|
|
return llvm::make_unique<InstPushArgument>(dvalue);
|
|
}
|
|
|
|
void InstStaticCall::Build(BasicBlockBuilder& bb_builder) const {
|
|
// inst = (StaticCall <function-symbol> <arg> ...)
|
|
|
|
SmallVector<Value*, 8> args;
|
|
size_t arg_count = args_len_;
|
|
while (arg_count) {
|
|
arg_count--;
|
|
args.push_back(bb_builder.PopArgument());
|
|
}
|
|
auto& builder = bb_builder.Builder();
|
|
|
|
// Hard code the function type for now.
|
|
auto& ctx = bb_builder.Context();
|
|
auto int8ty = IntegerType::getInt8Ty(ctx);
|
|
auto i8ptr = PointerType::get(int8ty, 0);
|
|
SmallVector<Type*, 8> arg_types;
|
|
arg_types.push_back(i8ptr);
|
|
auto print_type = FunctionType::get(Type::getVoidTy(ctx), arg_types, false);
|
|
|
|
// Get the function value we need.
|
|
auto func = bb_builder.GetValue(function_);
|
|
builder.CreateCall(print_type, func, args);
|
|
}
|
|
|
|
Expected<std::unique_ptr<DartInstruction>> InstStaticCall::Construct(
|
|
dart::SExpList* inst,
|
|
DartBasicBlockBuilder& bb_builder) {
|
|
// inst = (StaticCall <function_name> { args_len <N> })
|
|
if (inst->Length() != 2) {
|
|
return CreateError("StaticCall should have exactly 1 argument");
|
|
}
|
|
dart::SExpSymbol* func = inst->At(1)->AsSymbol();
|
|
if (!func) {
|
|
return CreateError("Expected StaticCall's argument to be a symbol");
|
|
}
|
|
const DartValue* dvalue = bb_builder.GetDef(func->value());
|
|
dart::SExpression* args_len_expr = inst->ExtraLookupValue("args_len");
|
|
// If args_len_expr isn't found args_len is assumed to be zero.
|
|
size_t args_len = 0;
|
|
if (args_len_expr) {
|
|
dart::SExpInteger* args_len_expr_int = args_len_expr->AsInteger();
|
|
if (args_len_expr_int) args_len = args_len_expr_int->value();
|
|
}
|
|
return llvm::make_unique<InstStaticCall>(dvalue, 1);
|
|
}
|
|
|
|
void InstReturn::Build(BasicBlockBuilder& bb_builder) const {
|
|
auto& builder = bb_builder.Builder();
|
|
builder.CreateRetVoid();
|
|
}
|
|
|
|
Expected<std::unique_ptr<DartInstruction>> InstReturn::Construct(
|
|
dart::SExpList* inst,
|
|
DartBasicBlockBuilder& bb_builder) {
|
|
// inst = (Return)
|
|
return llvm::make_unique<InstReturn>();
|
|
}
|
|
|
|
static Expected<StringMap<DartConstant>> MakeConstants(dart::SExpList* sexpr) {
|
|
dart::ApiZone zone;
|
|
StringMap<DartConstant> out;
|
|
for (intptr_t i = 1; i < sexpr->Length(); ++i) {
|
|
DartConstant constant;
|
|
dart::SExpList* def = sexpr->At(i)->AsList();
|
|
if (!def) {
|
|
return CreateError(Twine("Stray token in constants at location ") +
|
|
Twine(i) + sexpr->At(i)->ToCString(zone.GetZone()));
|
|
}
|
|
if (def->Length() != 3) {
|
|
return CreateError("Constant definitions must have exactly 3 lines");
|
|
}
|
|
dart::SExpSymbol* def_symbol = def->At(0)->AsSymbol();
|
|
if (def_symbol->value() != StringRef("def")) {
|
|
return CreateError(
|
|
"first element in a constant definition expected to be `def`");
|
|
}
|
|
dart::SExpSymbol* def_name = def->At(1)->AsSymbol();
|
|
if (!def_name) {
|
|
return CreateError("element after `def` in constant expected to be name");
|
|
}
|
|
dart::SExpression* def_value = def->At(2);
|
|
if (def_value->IsString()) {
|
|
constant.str = def_value->AsString()->value();
|
|
constant.type = DartConstant::Type::String;
|
|
} else {
|
|
return CreateError("We can't yet handle that element type");
|
|
}
|
|
out[def_name->value()] = constant;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
#define FOREACH_INSTRUCTION(M) \
|
|
M(CheckStackOverflow) \
|
|
M(PushArgument) \
|
|
M(StaticCall) \
|
|
M(Return)
|
|
|
|
static Expected<std::unique_ptr<DartInstruction>> MakeInstruction(
|
|
dart::SExpList* sexpr,
|
|
DartBasicBlockBuilder& bb_builder) {
|
|
if (sexpr->Length() < 1)
|
|
return CreateError("An empty list can't be an instruction");
|
|
dart::SExpSymbol* inst_sym = sexpr->At(0)->AsSymbol();
|
|
if (!inst_sym)
|
|
return CreateError(
|
|
"Expected first element of list in instruction to be a symbol");
|
|
using CtorFunc = std::function<Expected<std::unique_ptr<DartInstruction>>(
|
|
dart::SExpList*, DartBasicBlockBuilder&)>;
|
|
CtorFunc ctor = StringSwitch<CtorFunc>(inst_sym->value())
|
|
#define HANDLE_INSTRUCTION_CASE(INST) .Case(#INST, Inst##INST::Construct)
|
|
FOREACH_INSTRUCTION(HANDLE_INSTRUCTION_CASE);
|
|
#undef HANDLE_INSTRUCTION_CASE
|
|
return ctor(sexpr, bb_builder);
|
|
}
|
|
|
|
static Expected<DartBlock> MakeBlock(dart::SExpList* sexpr,
|
|
DartFunction& function,
|
|
const StringMap<const DartValue*>& env) {
|
|
// Construct the basic block builder
|
|
DartBasicBlockBuilder bb_builder;
|
|
for (const auto& c : function.constants)
|
|
bb_builder.AddDef(c.getKey(), &c.getValue());
|
|
for (const auto& c : env)
|
|
bb_builder.AddDef(c.getKey(), c.getValue());
|
|
|
|
DartBlock out;
|
|
|
|
// Make sure we have a basic block and get the name
|
|
if (sexpr->Length() <= 2)
|
|
return CreateError("too few elements in basic block");
|
|
dart::SExpSymbol* block_name = sexpr->At(1)->AsSymbol();
|
|
if (!block_name)
|
|
return CreateError("expected block name after `block` symbol");
|
|
out.name = block_name->value();
|
|
|
|
// Now construct each instruction and add it to the basic block
|
|
for (intptr_t i = 2; i < sexpr->Length(); ++i) {
|
|
dart::SExpList* inst = sexpr->At(i)->AsList();
|
|
if (!inst)
|
|
return CreateError("stray token at element " + Twine(i) +
|
|
" in basic block " + out.name);
|
|
auto inst_or = MakeInstruction(inst, bb_builder);
|
|
if (!inst_or) return inst_or.takeError();
|
|
out.instructions.emplace_back(std::move(*inst_or));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
Expected<DartFunction> MakeFunction(dart::SExpression* sexpr,
|
|
const StringMap<const DartValue*>& env) {
|
|
// Basic checking that this s-expression looks like a function
|
|
dart::ApiZone zone;
|
|
dart::SExpList* flist = sexpr->AsList();
|
|
if (!flist) return CreateError("S-Expression was not a function list");
|
|
if (flist->Length() < 2)
|
|
return CreateError("S-Expression list was too short to be a function");
|
|
dart::SExpSymbol* function_symbol = flist->At(0)->AsSymbol();
|
|
if (function_symbol == nullptr ||
|
|
function_symbol->value() != StringRef("function"))
|
|
return CreateError(
|
|
"S-Expression cannot be a function as it does not start with "
|
|
"`function`");
|
|
dart::SExpSymbol* function_name = flist->At(1)->AsSymbol();
|
|
if (function_name == nullptr)
|
|
return CreateError("Expected symbol name after `function` symbol");
|
|
|
|
// Now we fill in all the details
|
|
DartFunction function;
|
|
function.name = function_name->value();
|
|
Optional<StringRef> normal_entry;
|
|
for (intptr_t i = 2; i < flist->Length(); ++i) {
|
|
dart::SExpList* chunk = flist->At(i)->AsList();
|
|
// Everything is a list so far so error out on other options
|
|
if (!chunk)
|
|
return CreateError(Twine("Stray token in function at location ") +
|
|
Twine(i) + ": " +
|
|
flist->At(i)->ToCString(zone.GetZone()));
|
|
dart::SExpSymbol* chunk_symbol = chunk->At(0)->AsSymbol();
|
|
if (!chunk_symbol)
|
|
return CreateError(Twine("Expected element ") + Twine(i) +
|
|
" of function to start with a symbol");
|
|
StringRef chunk_tag = chunk_symbol->value();
|
|
|
|
if (chunk_tag == "constants") {
|
|
auto constants = MakeConstants(chunk);
|
|
if (!constants) return constants.takeError();
|
|
function.constants = std::move(*constants);
|
|
}
|
|
|
|
if (chunk_tag == "block") {
|
|
auto block = MakeBlock(chunk, function, env);
|
|
if (!block) return block.takeError();
|
|
StringRef name = block->name;
|
|
function.blocks[name] = std::move(*block);
|
|
}
|
|
|
|
if (chunk_tag == "normal-entry") {
|
|
if (chunk->Length() != 2)
|
|
return CreateError("Expected 1 argument to normal-entry");
|
|
dart::SExpSymbol* block_name = chunk->At(1)->AsSymbol();
|
|
if (!block_name)
|
|
return CreateError("expected block name after normal-entry symbol");
|
|
normal_entry = block_name->value();
|
|
}
|
|
}
|
|
|
|
if (normal_entry) {
|
|
auto iter = function.blocks.find(*normal_entry);
|
|
if (iter != function.blocks.end())
|
|
function.normal_entry = &iter->getValue();
|
|
} else {
|
|
function.normal_entry = nullptr;
|
|
}
|
|
|
|
return function;
|
|
}
|