blob: 5a1f0ecee6715fff9f9e1316dd66a69c697d9a42 [file] [log] [blame]
// Copyright (c) 2020, 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 "vm/compiler/aot/wasm_codegen.h"
#include "vm/object_store.h"
#define Z (zone_)
#define M (module_builder_)
namespace dart {
namespace {
using ::std::make_pair;
using ::std::pair;
using ::wasm::WasmTrace;
} // namespace
WasmCodegen::WasmCodegen(Precompiler* precompiler, Zone* zone)
: precompiler_(precompiler),
zone_(zone),
module_builder_(zone),
classes_(zone, 16),
class_to_wasm_class_info_(zone),
function_to_wasm_function_(zone),
field_to_wasm_field_(zone),
print_i64_func_(nullptr),
object_type_(nullptr),
object_rtt_(nullptr) {}
void WasmCodegen::Demo() {
// Make a Wasm struct type {i32, mut i64}.
wasm::StructType* const str_type = M.MakeStructType();
str_type->AddField(M.i32(), /*mut =*/false);
str_type->AddField(M.i64(), /*mut =*/true);
// Make a Wasm function type [i64 f32 f64] -> [i32].
wasm::FuncType* const fct_type = M.MakeFuncType();
fct_type->AddParam(M.i64());
fct_type->AddParam(M.f32());
fct_type->AddParam(M.f64());
fct_type->AddResult(M.i32());
// Make a Wasm function $fct, of type [i64 f32 f64] -> [i32].
wasm::Function* const fct = M.AddFunction("fct", fct_type);
wasm::InstructionList* const instrs = fct->MakeNewBodyAndClearLocals();
// Register four locals to this function.
// Note: Parameters are *not* checked against func_type.
// So, this function is not well-formed, but it doesn't matter
// for testing purposes.
// fct->AddLocal(wasm::Local::Kind::kParam, M.i32(),
// "x"); // (param $x i32)
fct->AddLocal(wasm::Local::Kind::kLocal,
M.MakeRefType(
/*nullable =*/true, M.MakeHeapType(str_type)),
"y1"); // (local $y (ref null str_type)).
// Note: Non-nullable reference local variables crash V8.
// Bug reported to jkummerow.
// fct->AddLocal(wasm::Local::Kind::kLocal,
// M.MakeRefType(
// /*nullable =*/false, M.MakeHeapType(str_type)),
// "y2"); // (local $y (ref str_type)).
// Note: V8 doesn't support any/anyref yet.
// fct->AddLocal(wasm::Local::Kind::kLocal, M.anyref(),
// "t0"); // (local $t1 anyref).
// Tests serialization/binary output for reference types.
fct->AddLocal(wasm::Local::Kind::kLocal, M.eqref(),
"t1"); // (local $t1 eqref).
// fct->AddLocal(wasm::Local::Kind::kLocal, M.i31ref(),
// "t2"); // (local $t2 i31ref).
// fct->AddLocal(wasm::Local::Kind::kLocal,
// M.MakeRefType(/*nullable = */ false, M.eq()),
// "q1"); // (local $q1 (ref eq)).
fct->AddLocal(wasm::Local::Kind::kLocal, M.funcref(),
"t2"); // (local $t2 funcref).
fct->AddLocal(wasm::Local::Kind::kLocal, M.externref(),
"t3"); // (local $t3 externref).
fct->AddLocal(wasm::Local::Kind::kLocal,
M.MakeRefType(/*nullable = */ true, M.i31()),
"t4"); // (local $t4 (ref null i31)).
// Add instructions to this function: compute 45 + 49.
instrs->AddI32Constant(45);
instrs->AddI32Constant(49);
instrs->AddIntOp(wasm::IntOp::IntegerKind::kI32, wasm::IntOp::OpKind::kAdd);
}
void WasmCodegen::HoistDefaultImports() {
wasm::FuncType* signature = M.MakeFuncType();
signature->AddParam(M.i64());
print_i64_func_ = M.AddImportedFunction("console", "log", signature);
}
void WasmCodegen::HoistBuiltinClasses() {
// Make an "Object" class.
HoistClass(Class::Handle(Type::Handle(Type::ObjectType()).type_class()));
// Make a "String" class.
HoistClass(Class::Handle(Type::Handle(Type::StringType()).type_class()));
}
void WasmCodegen::HoistClassesFromLibrary(const Library& lib) {
HANDLESCOPE(Thread::Current());
const Array& dict = Array::Handle(lib.dictionary());
const intptr_t dict_size = dict.Length() - 1; // To skip the top level class.
Object& entry = Object::Handle();
for (intptr_t i = 0; i < dict_size; ++i) {
entry = dict.At(i);
if (entry.IsNull()) {
continue;
}
if (entry.IsClass()) {
HoistClass(Class::Cast(entry));
}
}
}
void WasmCodegen::HoistFunctionsFromLibrary(const Library& lib) {
HANDLESCOPE(Thread::Current());
const Array& dict = Array::Handle(lib.dictionary());
const intptr_t dict_size = dict.Length() - 1; // To skip the top level class.
Object& entry = Object::Handle();
Function& function = Function::Handle();
Array& functions = Array::Handle();
for (intptr_t i = 0; i < dict_size; ++i) {
entry = dict.At(i);
if (entry.IsNull()) {
continue;
}
if (entry.IsClass()) {
functions ^= Class::Cast(entry).functions();
if (functions.IsNull()) {
continue;
}
for (intptr_t j = 0; j < functions.Length(); j++) {
function ^= functions.At(j);
if (function.IsNull()) {
continue;
}
WasmTrace("Hoisting METHOD: %s to Wasm module builder\n",
function.ToCString());
HoistFunction(function);
}
} else if (entry.IsFunction()) {
function ^= entry.raw();
WasmTrace("Hoisting FUNCTION: %s to Wasm module builder\n",
function.ToCString());
HoistFunction(function);
}
}
}
void WasmCodegen::GenerateWasmDispatchTable(const Array& code_array) {
HANDLESCOPE(Thread::Current());
// Add table to module.
M.AddFunctionsTable(code_array.Length(), code_array.Length());
// Iterate through elements in dispatch table and initialize the Wasm table.
auto& code = Code::Handle();
auto& function = Function::Handle();
for (intptr_t i = 0; i < code_array.Length(); i++) {
code = Code::RawCast(code_array.At(i));
if (code.IsNull()) {
continue;
}
if (!code.IsFunctionCode()) {
continue;
}
function = code.function();
ASSERT(!function.IsNull());
wasm::Function* const wasm_function = GetWasmFunction(function);
if (wasm_function == nullptr) continue;
M.AddElemTableInitializer(i, wasm_function);
WasmTrace("Wasm dispatch table has function %" Pu32 " at position %" Pd
"\n",
wasm_function->index(), i);
}
}
void WasmCodegen::GenerateClassLayoutsAndRtts() {
HANDLESCOPE(Thread::Current());
for (intptr_t i = 0; i < classes_.length(); ++i) {
const Class& klass = *classes_.At(i);
GenerateClassLayoutAndRtt(klass);
}
}
WasmClassInfo& WasmCodegen::GetWasmClassInfo(const Class& klass) {
// Treat dynamic as Object.
Class& klass2 = Class::Handle(klass.raw());
if (klass.IsDynamicClass()) {
klass2 = Thread::Current()->isolate()->object_store()->object_class();
}
pair<const Class*, WasmClassInfo>* const pair =
class_to_wasm_class_info_.Lookup(&klass2);
// At this point all classes should have been hoisted.
// If this is not true, exit with an error even in non-debug builds.
if (pair == nullptr) {
FATAL1("WasmCodegen::GetWasmClassInfo called on non-hoisted class %s",
klass.ToCString());
}
return pair->second;
}
wasm::ValueType* WasmCodegen::GetWasmType(const Class& klass) {
// Integers and booleans are always unboxed in this implementation.
if (IsIntegerClass(klass)) {
return M.i64();
}
if (IsBoolClass(klass)) {
return M.i32();
}
wasm::StructType* const wasm_struct = GetWasmClassInfo(klass).struct_type_;
// At this point all classes which appear as fields should have been
// hoisted. If this is not true, exit with an error even in non-debug
// builds.
if (wasm_struct == nullptr) {
FATAL1("missing for %s", klass.ToCString());
}
return M.MakeRefType(/*nullable =*/true, wasm_struct);
}
wasm::Function* WasmCodegen::GetWasmFunction(const Function& function) {
return function_to_wasm_function_.LookupValue(&function);
}
wasm::Field* WasmCodegen::GetWasmField(const Field& field) {
return field_to_wasm_field_.LookupValue(&field);
}
SExpression* WasmCodegen::Serialize(Zone* zone) {
return module_builder_.Serialize(zone);
}
void WasmCodegen::OutputBinary(WriteStream* stream) {
module_builder_.OutputBinary(stream);
}
void WasmCodegen::HoistClass(const Class& klass) {
WasmTrace("Hoisting CLASS: %s to Wasm module builder, with cid = %" Pd "\n",
klass.ToCString(), klass.id());
wasm::StructType* wasm_struct = M.MakeStructType();
const Class& persistent_class = Class::ZoneHandle(Z, klass.raw());
classes_.Add(&persistent_class);
class_to_wasm_class_info_.Insert(
make_pair(&persistent_class, WasmClassInfo(wasm_struct, nullptr)));
}
void WasmCodegen::HoistFunction(const Function& function) {
wasm::FuncType* signature = MakeDummySignature();
const String& function_name = String::ZoneHandle(Z, function.name());
wasm::Function* wasm_function =
M.AddFunction(function_name.ToCString(), signature);
if (strcmp(function_name.ToCString(), "main") == 0) {
if (M.start_function() != nullptr) {
FATAL("Multiple main functions detected");
}
M.set_start_function(wasm_function);
}
// TODO(andreicostin): Normally, we would leave hoisted functions bodyless
// until the Wasm compiler pass compiles a body for them, but for demo
// purposes, we'll just create a demo body for them which is consistent
// with the dummy signature defined below ([] -> [i64]).
wasm_function->MakeNewBodyAndClearLocals()->AddI64Constant(100);
function_to_wasm_function_.Insert(
make_pair(&Function::ZoneHandle(Z, function.raw()), wasm_function));
}
void WasmCodegen::GenerateClassLayoutAndRtt(const Class& klass) {
if (klass.IsNull()) {
FATAL("GenerateClassLayoutAndRtt called with Null class");
}
WasmTrace("GenerateClassLayoutAndRtt reached class %s, with cid = %" Pd "\n",
klass.ToCString(), klass.id());
WasmClassInfo& wasm_class_info = GetWasmClassInfo(klass);
wasm::StructType* const wasm_struct = wasm_class_info.struct_type_;
wasm::Global*& wasm_rtt = wasm_class_info.rtt_definition_;
// Memoization - if class has already had its layout generated, exit.
if (!wasm_struct->fields().is_empty()) {
return;
}
// Rtts shouldn't have been allocated before the struct
// layout has been computed.
RELEASE_ASSERT(wasm_rtt == nullptr);
WasmTrace("Generating Wasm layout and rtt for class %s, with cid = %" Pd "\n",
klass.ToCString(), klass.id());
// Special handling for root class "Object".
if (klass.IsObjectClass()) {
object_type_ = M.MakeRefType(/*nullable =*/true, wasm_struct);
// For class id.
wasm_struct->AddField(M.i32(), /*mut =*/true);
// Rtt - root of class hierarchy.
object_rtt_ = M.MakeRttCanon(wasm_struct);
wasm_rtt = object_rtt_;
return;
}
// Special handling for Strings. Largely unimplemented for now.
if (IsStringClass(klass)) {
// For class id.
wasm_struct->AddField(M.i32(), /*mut =*/true);
// Rtt - parent is "Object".
wasm_rtt = M.MakeRttChild(wasm_struct, object_rtt_);
return;
}
const Class& parent_class = Class::Handle(klass.SuperClass());
// Ensure that the parent class has had its layout and rtt generated
// before continuing.
GenerateClassLayoutAndRtt(parent_class);
// Get parent struct and rtt.
WasmClassInfo& parent_wasm_class_info = GetWasmClassInfo(parent_class);
wasm::StructType* const parent_wasm_struct =
parent_wasm_class_info.struct_type_;
// Parent struct should have been allocated.
if (parent_wasm_struct == nullptr) {
FATAL1("parent_wasm_struct missing for parent class %s",
parent_class.ToCString());
}
wasm::Global* const parent_wasm_rtt = parent_wasm_class_info.rtt_definition_;
// Parent rtt should have been allocated.
if (parent_wasm_rtt == nullptr) {
FATAL1("parent_wasm_rtt missing for parent class %s",
parent_class.ToCString());
}
// Allocate rtt.
wasm_rtt = M.MakeRttChild(wasm_struct, parent_wasm_rtt);
// Allocate struct layout.
parent_wasm_struct->CopyFieldsTo(wasm_struct);
const Array& fields = Array::Handle(klass.fields());
Field& field = Field::Handle();
Type& type = Type::Handle();
Class& type_class = Class::Handle();
for (intptr_t i = 0; i < fields.Length(); ++i) {
field ^= fields.At(i);
type ^= field.type();
type_class = type.type_class();
WasmTrace("--> Processing field %s\n", field.ToCString());
wasm::ValueType* const field_type = GetWasmType(type_class);
wasm::Field* const wasm_field = wasm_struct->AddField(field_type,
/*mut =*/true);
field_to_wasm_field_.Insert(
make_pair(&Field::ZoneHandle(Z, field.raw()), wasm_field));
}
}
wasm::FuncType* WasmCodegen::MakeDummySignature() {
wasm::FuncType* const func_type = M.MakeFuncType();
func_type->AddResult(M.i64());
return func_type;
}
wasm::FuncType* WasmCodegen::MakeSignature(const Function& function) {
const intptr_t num_params = function.NumParameters();
const AbstractType& result_type =
AbstractType::Handle(function.result_type());
const Class& result_type_class = Class::Handle(result_type.type_class());
const String& function_name = String::Handle(function.name());
wasm::FuncType* const func_type = M.MakeFuncType();
// The main function should have signature "void main()" in our implementation.
if (strcmp(function_name.ToCString(), "main") == 0) {
if (num_params > 0 || !result_type.IsVoidType()) {
FATAL("Dart main function should have signature void main()");
}
return func_type;
}
// Translate function arguments.
Type& type = Type::Handle();
Class& type_class = Class::Handle();
for (intptr_t i = 0; i < num_params; ++i) {
type ^= function.ParameterTypeAt(i);
type_class ^= type.type_class();
if (function.HasThisParameter() && i == 0) {
// Due to Wasm requiring a type for call_indirect which is at least
// as general as all possible receiver function types at an instance call
// site, we choose (for simplicity) to not specialize the type of the
// "this" argument, but rather downcast in the prelude of each function.
func_type->AddParam(object_type_);
} else {
func_type->AddParam(GetWasmType(type_class));
}
}
// Translate return value.
// A Dart return value of "Void" should translate
// into no return value in Wasm.
WasmTrace("Return value WasmTrace for function %s has returned type %s",
function.ToCString(), result_type_class.ToCString());
if (!result_type_class.IsVoidClass()) {
func_type->AddResult(GetWasmType(result_type_class));
}
return func_type;
}
bool WasmCodegen::IsIntegerClass(const Class& klass) {
return klass.raw() == Type::Handle(Type::IntType()).type_class() ||
IsIntegerClassId(klass.id());
}
bool WasmCodegen::IsBoolClass(const Class& klass) {
return klass.raw() == Type::Handle(Type::BoolType()).type_class();
}
bool WasmCodegen::IsStringClass(const Class& klass) {
return klass.raw() == Type::Handle(Type::StringType()).type_class() ||
IsStringClassId(klass.id());
}
} // namespace dart