Commit 67c7c416 by Eric Holk

Subzero. WASM. Additional progress.

This change includes a number of improvements since the last WASM CL. It compiles against a newer version of V8 that matches the current WASM binary format. Many more WASM instructions are supported, as well as global variable initializers. There is also the beginning of a runtime library that implements some system calls required by the WASM C library. The c2wasm-exe.sh script can be used to compile a C program to a .wasm module, which is then compiled by Subzero into a native executable. This change includes a new Breakpoint instruction, which inserts an unconditional breakpoint into the executable. This has been helpful in debugging code generation for some WASM instructions. The Breakpoint instruction is only completely implemented on X86. BUG= https://bugs.chromium.org/p/nativeclient/issues/detail?id=4369 R=stichnot@chromium.org Review URL: https://codereview.chromium.org/1876413002 .
parent 397f602c
......@@ -11,3 +11,7 @@
/pnacl-sz
/build/
/crosstest/Output/
# Ignore WASM torture tests
/torture-s/
/torture-s2wasm-sexpr-wasm/
......@@ -256,15 +256,13 @@ endif
BASE_CXXFLAGS := -std=gnu++11 -Wall -Wextra -fno-rtti \
-fno-exceptions $(OPTLEVEL) $(ASSERTIONS) -g -pedantic \
$(LLVM_EXTRA_WARNINGS) $(CXX_EXTRA) -MP -MD
$(LLVM_EXTRA_WARNINGS) $(CXX_EXTRA) -MP -MD -Werror
ifdef WASM
# The WASM code inherits a lot of V8 code, which does not compile with
# -Werror.
BASE_CXXFLAGS := $(BASE_CXXFLAGS) $(V8_CXXFLAGS) -DALLOW_WASM=1
OBJDIR := $(OBJDIR)+Wasm
else
BASE_CXXFLAGS := $(BASE_CXXFLAGS) -Werror -DALLOW_WASM=0
BASE_CXXFLAGS := $(BASE_CXXFLAGS) -DALLOW_WASM=0
endif
CXXFLAGS := $(LLVM_CXXFLAGS) $(BASE_CXXFLAGS) $(CXX_DEFINES) $(HOST_FLAGS) \
......
......@@ -18,6 +18,24 @@ You'll need to build v8 as a shared library. Build it like this:
make -j48 native component=shared_library
```
`wasm-run-torture-tests.py` can be used to run all the tests, or some subset.
Running a subset will enable verbose output. You can download the torture tests
from the [WebAssembly waterfall](https://wasm-stat.us/console).
`wasm-run-torture-tests.py` can be used to run all the tests, or some
subset. Running a subset will enable verbose output. You can download the
torture tests from the [WebAssembly waterfall](https://wasm-stat.us/console) or
by running `./fetch-torture-tests.sh`.
To compile and run an executable, do the following from the subzero directory.
```
./pnacl-sz -filetype=obj -o foo.o foo.wasm
clang -m32 foo.o src/wasm-runtime.c
./a.out
```
Other useful commands:
Compile a C file to a .wasm
```
./wasm-install/bin/emscripten/emcc hello-wasm.c -s BINARYEN=1
./wasm-install/bin/sexpr-wasm a.out.wast -o a.out.wasm
```
#!/bin/bash
# TODO (eholk): This script is a hack for debugging and development
# that should be removed.
./wasm-install/bin/emscripten/emcc "$1" -s BINARYEN=1 \
-s 'BINARYEN_METHOD="native-wasm"' -O2 && \
./wasm-install/bin/sexpr-wasm a.out.wast -o a.out.wasm && \
./pnacl-sz a.out.wasm -o a.out.o -filetype=obj -O2 && \
clang -m32 a.out.o runtime/wasm-runtime.c -g
#!/bin/bash
BUILDID=4915
BUILD_PATH=https://storage.googleapis.com/wasm-llvm/builds/git
wget -O - /wasm-torture-s-$BUILDID.tbz2 \
| tar xj
wget -O - $BUILD_PATH/wasm-torture-s2wasm-sexpr-wasm-$BUILDID.tbz2 \
| tar xj
wget -O - $BUILD_PATH/wasm-binaries-$BUILDID.tbz2 \
| tar xj
......@@ -22,13 +22,13 @@ def run_test(test_file, verbose=False):
global fail_count
cmd = """LD_LIBRARY_PATH=../../../../v8/out/native/lib.target ./pnacl-sz \
-filetype=asm -target=arm32 {} -threads=0 -O2 \
-filetype=asm -target=x8632 {} -threads=0 -O2 \
-verbose=wasm""".format(test_file)
if not verbose:
cmd += " &> /dev/null"
sys.stdout.write(test_file + "...");
sys.stdout.write(test_file + " ...");
status = os.system(cmd);
if status != 0:
fail_count += 1
......@@ -45,7 +45,7 @@ if len(sys.argv) > 1:
test_files = sys.argv[1:]
verbose = True
else:
test_files = glob.glob("./torture-s2wasm-sexpr-wasm.old/*.wasm")
test_files = glob.glob("./torture-s2wasm-sexpr-wasm/*.wasm")
for test_file in test_files:
run_test(test_file, verbose)
......
//===- subzero/runtime/wasm-runtime.c - Subzero WASM runtime source -------===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the system calls required by the libc that is included
// in WebAssembly programs.
//
//===----------------------------------------------------------------------===//
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern char WASM_MEMORY[];
void env$$abort() {
fprintf(stderr, "Aborting...\n");
abort();
}
void env$$_abort() { env$$abort(); }
#define UNIMPLEMENTED(f) \
void env$$##f() { \
fprintf(stderr, "Unimplemented: " #f "\n"); \
abort(); \
}
UNIMPLEMENTED(sbrk)
UNIMPLEMENTED(pthread_cleanup_push)
UNIMPLEMENTED(pthread_cleanup_pop)
UNIMPLEMENTED(pthread_self)
UNIMPLEMENTED(__lock)
UNIMPLEMENTED(__unlock)
UNIMPLEMENTED(__syscall6)
UNIMPLEMENTED(__syscall140)
void *wasmPtr(int Index) {
// TODO (eholk): get the mask from the WASM file.
const int MASK = 0x3fffff;
Index &= MASK;
return WASM_MEMORY + Index;
}
extern int __szwasm_main(int, char **);
#define WASM_REF(Type, Index) ((Type *)wasmPtr(Index))
#define WASM_DEREF(Type, Index) (*WASM_REF(Type, Index))
int main(int argc, char **argv) { return __szwasm_main(argc, argv); }
/// sys_write
int env$$__syscall4(int Which, int VarArgs) {
int Fd = WASM_DEREF(int, VarArgs + 0 * sizeof(int));
int Buffer = WASM_DEREF(int, VarArgs + 1 * sizeof(int));
int Length = WASM_DEREF(int, VarArgs + 2 * sizeof(int));
return write(Fd, WASM_REF(char *, Buffer), Length);
}
/// sys_ioctl
int env$$__syscall54(int A, int B) {
int Fd = WASM_DEREF(int, B + 0 * sizeof(int));
int Op = WASM_DEREF(int, B + 1 * sizeof(int));
int ArgP = WASM_DEREF(int, B + 2 * sizeof(int));
// TODO (eholk): implement sys_ioctl
return -ENOTTY;
}
int env$$__syscall146(int Which, int VarArgs) {
fprintf(stderr, "syscall146\n");
int Fd = WASM_DEREF(int, VarArgs);
int Iov = WASM_DEREF(int, VarArgs + sizeof(int));
int Iovcnt = WASM_DEREF(int, VarArgs + 2 * sizeof(int));
fprintf(stderr, " Fd=%d, Iov=%d (%p), Iovcnt=%d\n", Fd, Iov, wasmPtr(Iov),
Iovcnt);
int Count = 0;
for (int I = 0; I < Iovcnt; ++I) {
void *Ptr = WASM_REF(void, WASM_DEREF(int, Iov + I * 8));
int Length = WASM_DEREF(int, Iov + I * 8 + 4);
fprintf(stderr, " [%d] write(%d, %p, %d) = ", I, Fd, Ptr, Length);
int Curr = write(Fd, Ptr, Length);
fprintf(stderr, "%d\n", Curr);
if (Curr < 0) {
return -1;
}
Count += Curr;
}
fprintf(stderr, " Count = %d\n", Count);
return Count;
}
......@@ -599,6 +599,7 @@ void Cfg::processAllocas(bool SortAndCombine) {
TimerMarker _(TimerStack::TT_alloca, this);
const uint32_t StackAlignment = getTarget()->getStackAlignment();
CfgNode *EntryNode = getEntryNode();
assert(EntryNode);
// LLVM enforces power of 2 alignment.
assert(llvm::isPowerOf2_32(StackAlignment));
// Determine if there are large alignment allocations in the entry block or
......
......@@ -105,6 +105,7 @@ const char *Inst::getInstName() const {
X(Store, "store");
X(Switch, "switch");
X(Assign, "assign");
X(Breakpoint, "break");
X(BundleLock, "bundlelock");
X(BundleUnlock, "bundleunlock");
X(FakeDef, "fakedef");
......@@ -516,8 +517,10 @@ void InstSwitch::addBranch(SizeT CaseIndex, uint64_t Value, CfgNode *Label) {
NodeList InstSwitch::getTerminatorEdges() const {
NodeList OutEdges;
OutEdges.reserve(NumCases + 1);
assert(LabelDefault);
OutEdges.push_back(LabelDefault);
for (SizeT I = 0; I < NumCases; ++I) {
assert(Labels[I]);
OutEdges.push_back(Labels[I]);
}
std::sort(OutEdges.begin(), OutEdges.end(),
......@@ -1047,6 +1050,9 @@ void InstTarget::dump(const Cfg *Func) const {
Inst::dump(Func);
}
InstBreakpoint::InstBreakpoint(Cfg *Func)
: InstHighLevel(Func, Inst::Breakpoint, 0, nullptr) {}
bool checkForRedundantAssign(const Variable *Dest, const Operand *Source) {
const auto *SrcVar = llvm::dyn_cast<const Variable>(Source);
if (!SrcVar)
......
......@@ -62,6 +62,7 @@ public:
Store,
Switch,
Assign, // not part of LLVM/PNaCl bitcode
Breakpoint, // not part of LLVM/PNaCl bitcode
BundleLock, // not part of LLVM/PNaCl bitcode
BundleUnlock, // not part of LLVM/PNaCl bitcode
FakeDef, // not part of LLVM/PNaCl bitcode
......@@ -976,6 +977,29 @@ private:
GlobalString FuncName;
};
/// This instruction inserts an unconditional breakpoint.
///
/// On x86, this assembles into an INT 3 instruction.
///
/// This instruction is primarily meant for debugging the code generator.
class InstBreakpoint : public InstHighLevel {
public:
InstBreakpoint() = delete;
InstBreakpoint(const InstBreakpoint &) = delete;
InstBreakpoint &operator=(const InstBreakpoint &) = delete;
InstBreakpoint(Cfg *Func);
public:
static InstBreakpoint *create(Cfg *Func) {
return new (Func->allocate<InstBreakpoint>()) InstBreakpoint(Func);
}
static bool classof(const Inst *Instr) {
return Instr->getKind() == Breakpoint;
}
};
/// The Target instruction is the base class for all target-specific
/// instructions.
class InstTarget : public Inst {
......
......@@ -107,6 +107,7 @@ template <typename TraitsType> struct InstImpl {
Imul,
ImulImm,
Insertps,
Int3,
Jmp,
Label,
Lea,
......@@ -2442,6 +2443,27 @@ template <typename TraitsType> struct InstImpl {
explicit InstX86UD2(Cfg *Func);
};
/// Int3 instruction.
class InstX86Int3 final : public InstX86Base {
InstX86Int3() = delete;
InstX86Int3(const InstX86Int3 &) = delete;
InstX86Int3 &operator=(const InstX86Int3 &) = delete;
public:
static InstX86Int3 *create(Cfg *Func) {
return new (Func->allocate<InstX86Int3>()) InstX86Int3(Func);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return InstX86Base::isClassof(Instr, InstX86Base::Int3);
}
private:
explicit InstX86Int3(Cfg *Func);
};
/// Test instruction.
class InstX86Test final : public InstX86Base {
InstX86Test() = delete;
......@@ -2914,6 +2936,7 @@ template <typename TraitsType> struct Insts {
using Icmp = typename InstImpl<TraitsType>::InstX86Icmp;
using Ucomiss = typename InstImpl<TraitsType>::InstX86Ucomiss;
using UD2 = typename InstImpl<TraitsType>::InstX86UD2;
using Int3 = typename InstImpl<TraitsType>::InstX86Int3;
using Test = typename InstImpl<TraitsType>::InstX86Test;
using Mfence = typename InstImpl<TraitsType>::InstX86Mfence;
using Store = typename InstImpl<TraitsType>::InstX86Store;
......
......@@ -263,6 +263,10 @@ InstImpl<TraitsType>::InstX86UD2::InstX86UD2(Cfg *Func)
: InstX86Base(Func, InstX86Base::UD2, 0, nullptr) {}
template <typename TraitsType>
InstImpl<TraitsType>::InstX86Int3::InstX86Int3(Cfg *Func)
: InstX86Base(Func, InstX86Base::Int3, 0, nullptr) {}
template <typename TraitsType>
InstImpl<TraitsType>::InstX86Test::InstX86Test(Cfg *Func, Operand *Src1,
Operand *Src2)
: InstX86Base(Func, InstX86Base::Test, 2, nullptr) {
......@@ -1780,6 +1784,30 @@ void InstImpl<TraitsType>::InstX86UD2::dump(const Cfg *Func) const {
}
template <typename TraitsType>
void InstImpl<TraitsType>::InstX86Int3::emit(const Cfg *Func) const {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrEmit();
assert(this->getSrcSize() == 0);
Str << "\t"
"int 3";
}
template <typename TraitsType>
void InstImpl<TraitsType>::InstX86Int3::emitIAS(const Cfg *Func) const {
Assembler *Asm = Func->getAssembler<Assembler>();
Asm->int3();
}
template <typename TraitsType>
void InstImpl<TraitsType>::InstX86Int3::dump(const Cfg *Func) const {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
Str << "int 3";
}
template <typename TraitsType>
void InstImpl<TraitsType>::InstX86Test::emit(const Cfg *Func) const {
if (!BuildDefs::dump())
return;
......
......@@ -400,6 +400,9 @@ void TargetLowering::lower() {
case Inst::Br:
lowerBr(llvm::cast<InstBr>(Instr));
break;
case Inst::Breakpoint:
lowerBreakpoint(llvm::cast<InstBreakpoint>(Instr));
break;
case Inst::Call:
lowerCall(llvm::cast<InstCall>(Instr));
break;
......
......@@ -375,6 +375,7 @@ protected:
virtual void lowerArithmetic(const InstArithmetic *Instr) = 0;
virtual void lowerAssign(const InstAssign *Instr) = 0;
virtual void lowerBr(const InstBr *Instr) = 0;
virtual void lowerBreakpoint(const InstBreakpoint *Instr) = 0;
virtual void lowerCall(const InstCall *Instr) = 0;
virtual void lowerCast(const InstCast *Instr) = 0;
virtual void lowerFcmp(const InstFcmp *Instr) = 0;
......
......@@ -5818,6 +5818,10 @@ void TargetARM32::lowerSwitch(const InstSwitch *Instr) {
_br(Instr->getLabelDefault());
}
void TargetARM32::lowerBreakpoint(const InstBreakpoint *Instr) {
UnimplementedLoweringError(this, Instr);
}
void TargetARM32::lowerUnreachable(const InstUnreachable * /*Instr*/) {
_trap();
}
......
......@@ -278,6 +278,7 @@ protected:
Operand *Val);
void lowerAtomicRMW(Variable *Dest, uint32_t Operation, Operand *Ptr,
Operand *Val);
void lowerBreakpoint(const InstBreakpoint *Instr) override;
void lowerIntrinsicCall(const InstIntrinsicCall *Instr) override;
void lowerInsertElement(const InstInsertElement *Instr) override;
void lowerLoad(const InstLoad *Instr) override;
......
......@@ -1155,6 +1155,10 @@ void TargetMIPS32::lowerSwitch(const InstSwitch *Instr) {
UnimplementedLoweringError(this, Instr);
}
void TargetMIPS32::lowerBreakpoint(const InstBreakpoint *Instr) {
UnimplementedLoweringError(this, Instr);
}
void TargetMIPS32::lowerUnreachable(const InstUnreachable *Instr) {
UnimplementedLoweringError(this, Instr);
}
......
......@@ -287,6 +287,7 @@ protected:
Operand *Src0, Operand *Src1);
void lowerAssign(const InstAssign *Instr) override;
void lowerBr(const InstBr *Instr) override;
void lowerBreakpoint(const InstBreakpoint *Instr) override;
void lowerCall(const InstCall *Instr) override;
void lowerCast(const InstCast *Instr) override;
void lowerExtractElement(const InstExtractElement *Instr) override;
......
......@@ -247,6 +247,7 @@ protected:
void lowerArithmetic(const InstArithmetic *Instr) override;
void lowerAssign(const InstAssign *Instr) override;
void lowerBr(const InstBr *Instr) override;
void lowerBreakpoint(const InstBreakpoint *Instr) override;
void lowerCall(const InstCall *Instr) override;
void lowerCast(const InstCast *Instr) override;
void lowerExtractElement(const InstExtractElement *Instr) override;
......@@ -656,6 +657,7 @@ protected:
AutoMemorySandboxer<> _(this, &Dest, &Src0, &Src1);
Context.insert<typename Traits::Insts::Insertps>(Dest, Src0, Src1);
}
void _int3() { Context.insert<typename Traits::Insts::Int3>(); }
void _jmp(Operand *Target) {
AutoMemorySandboxer<> _(this, &Target);
Context.insert<typename Traits::Insts::Jmp>(Target);
......
......@@ -6096,6 +6096,12 @@ void TargetX86Base<TraitsType>::lowerUnreachable(
}
template <typename TraitsType>
void TargetX86Base<TraitsType>::lowerBreakpoint(
const InstBreakpoint * /*Instr*/) {
_int3();
}
template <typename TraitsType>
void TargetX86Base<TraitsType>::lowerRMW(const InstX86FakeRMW *RMW) {
// If the beacon variable's live range does not end in this instruction, then
// it must end in the modified Store instruction that follows. This means
......
......@@ -17,14 +17,33 @@
#if ALLOW_WASM
#include "llvm/Support/StreamingMemoryObject.h"
#include "WasmTranslator.h"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wcovered-switch-default"
#endif // __clang__
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#endif // defined(__GNUC__) && !defined(__clang__)
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/zone.h"
#include "src/bit-vector.h"
#include "src/wasm/ast-decoder-impl.h"
#ifdef __clang__
#pragma clang diagnostic pop
#endif // __clang__
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif // defined(__GNUC__) && !defined(__clang__)
#include "IceCfgNode.h"
#include "IceGlobalInits.h"
......@@ -35,21 +54,17 @@ using namespace v8::internal;
using namespace v8::internal::wasm;
using v8::internal::wasm::DecodeWasmModule;
#include "src/wasm/ast-decoder-impl.h"
#undef LOG
#define LOG(Expr) log([&](Ostream & out) { Expr; })
namespace {
Ice::Type toIceType(v8::internal::MachineType) {
// TODO(eholk): actually convert this.
return IceType_i32;
std::string toStdString(WasmName Name) {
return std::string(Name.name, Name.length);
}
Ice::Type toIceType(wasm::LocalType Type) {
switch (Type) {
default:
llvm::report_fatal_error("unexpected enum value");
case MachineRepresentation::kNone:
llvm::report_fatal_error("kNone type not supported");
case MachineRepresentation::kBit:
......@@ -71,6 +86,57 @@ Ice::Type toIceType(wasm::LocalType Type) {
case MachineRepresentation::kTagged:
llvm::report_fatal_error("kTagged type not supported");
}
llvm::report_fatal_error("unexpected type");
}
Ice::Type toIceType(v8::internal::MachineType Type) {
// TODO (eholk): reorder these based on expected call frequency.
if (Type == MachineType::Int32()) {
return IceType_i32;
}
if (Type == MachineType::Uint32()) {
return IceType_i32;
}
if (Type == MachineType::Int8()) {
return IceType_i8;
}
if (Type == MachineType::Uint8()) {
return IceType_i8;
}
if (Type == MachineType::Int16()) {
return IceType_i16;
}
if (Type == MachineType::Uint16()) {
return IceType_i16;
}
if (Type == MachineType::Int64()) {
return IceType_i64;
}
if (Type == MachineType::Uint64()) {
return IceType_i64;
}
if (Type == MachineType::Float32()) {
return IceType_f32;
}
if (Type == MachineType::Float64()) {
return IceType_f64;
}
llvm::report_fatal_error("Unsupported MachineType");
}
std::string fnNameFromId(uint32_t Id) {
return std::string("fn") + to_string(Id);
}
std::string getFunctionName(const WasmModule *Module, uint32_t func_index) {
// Try to find the function name in the export table
for (const auto Export : Module->export_table) {
if (Export.func_index == func_index) {
return "__szwasm_" + toStdString(Module->GetName(Export.name_offset,
Export.name_length));
}
}
return fnNameFromId(func_index);
}
} // end of anonymous namespace
......@@ -132,7 +198,11 @@ public:
Ostream &operator<<(Ostream &Out, const OperandNode &Op) {
if (Op.isOperand()) {
Out << "(Operand*)" << Op.toOperand();
const auto *Oper = Op.toOperand();
Out << "(Operand*)" << Oper;
if (Oper) {
Out << "::" << Oper->getType();
}
} else if (Op.isCfgNode()) {
Out << "(CfgNode*)" << Op.toCfgNode();
} else {
......@@ -141,7 +211,7 @@ Ostream &operator<<(Ostream &Out, const OperandNode &Op) {
return Out;
}
constexpr bool isComparison(wasm::WasmOpcode Opcode) {
bool isComparison(wasm::WasmOpcode Opcode) {
switch (Opcode) {
case kExprI32Ne:
case kExprI64Ne:
......@@ -157,6 +227,16 @@ constexpr bool isComparison(wasm::WasmOpcode Opcode) {
case kExprI64GtS:
case kExprI32GtU:
case kExprI64GtU:
case kExprF32Ne:
case kExprF64Ne:
case kExprF32Le:
case kExprF64Le:
case kExprI32LeS:
case kExprI64LeS:
case kExprI32GeU:
case kExprI64GeU:
case kExprI32LeU:
case kExprI64LeU:
return true;
default:
return false;
......@@ -172,7 +252,7 @@ class IceBuilder {
public:
explicit IceBuilder(class Cfg *Func)
: Func(Func), Ctx(Func->getContext()), ControlPtr(nullptr) {}
: ControlPtr(nullptr), Func(Func), Ctx(Func->getContext()) {}
/// Allocates a buffer of Nodes for use by V8.
Node *Buffer(size_t Count) {
......@@ -181,14 +261,14 @@ public:
}
Node Error() { llvm::report_fatal_error("Error"); }
Node Start(unsigned Params) {
Node Start(uint32_t Params) {
LOG(out << "Start(" << Params << ") = ");
auto *Entry = Func->makeNode();
Func->setEntryNode(Entry);
auto *Entry = Func->getEntryNode();
assert(Entry);
LOG(out << Node(Entry) << "\n");
return OperandNode(Entry);
}
Node Param(unsigned Index, wasm::LocalType Type) {
Node Param(uint32_t Index, wasm::LocalType Type) {
LOG(out << "Param(" << Index << ") = ");
auto *Arg = makeVariable(toIceType(Type));
assert(Index == NextArg);
......@@ -208,25 +288,25 @@ public:
LOG(out << "Terminate(" << Effect << ", " << Control << ")"
<< "\n");
}
Node Merge(unsigned Count, Node *Controls) {
Node Merge(uint32_t Count, Node *Controls) {
LOG(out << "Merge(" << Count);
for (unsigned i = 0; i < Count; ++i) {
for (uint32_t i = 0; i < Count; ++i) {
LOG(out << ", " << Controls[i]);
}
LOG(out << ") = ");
auto *MergedNode = Func->makeNode();
for (unsigned i = 0; i < Count; ++i) {
for (uint32_t i = 0; i < Count; ++i) {
CfgNode *Control = Controls[i];
Control->appendInst(InstBr::create(Func, MergedNode));
}
LOG(out << (OperandNode)MergedNode << "\n");
return OperandNode(MergedNode);
}
Node Phi(wasm::LocalType Type, unsigned Count, Node *Vals, Node Control) {
Node Phi(wasm::LocalType, uint32_t Count, Node *Vals, Node Control) {
LOG(out << "Phi(" << Count << ", " << Control);
for (int i = 0; i < Count; ++i) {
for (uint32_t i = 0; i < Count; ++i) {
LOG(out << ", " << Vals[i]);
}
LOG(out << ") = ");
......@@ -243,7 +323,7 @@ public:
// TODO(eholk): find a better way besides multiplying by some arbitrary
// constant.
auto *Phi = InstPhi::create(Func, Count * 10, Dest);
for (int i = 0; i < Count; ++i) {
for (uint32_t i = 0; i < Count; ++i) {
auto *Op = Vals[i].toOperand();
assert(Op);
Phi->addArgument(Op, InEdges[i]);
......@@ -253,10 +333,10 @@ public:
LOG(out << Node(Dest) << "\n");
return OperandNode(Dest);
}
Node EffectPhi(unsigned Count, Node *Effects, Node Control) {
Node EffectPhi(uint32_t Count, Node *Effects, Node Control) {
// TODO(eholk): this function is almost certainly wrong.
LOG(out << "EffectPhi(" << Count << ", " << Control << "):\n");
for (unsigned i = 0; i < Count; ++i) {
for (uint32_t i = 0; i < Count; ++i) {
LOG(out << " " << Effects[i] << "\n");
}
return OperandNode(nullptr);
......@@ -294,13 +374,18 @@ public:
LOG(out << "Binop(" << WasmOpcodes::OpcodeName(Opcode) << ", " << Left
<< ", " << Right << ") = ");
auto *Dest = makeVariable(
isComparison(Opcode) ? IceType_i1 : Left.toOperand()->getType());
isComparison(Opcode) ? IceType_i32 : Left.toOperand()->getType());
switch (Opcode) {
case kExprI32Add:
case kExprI64Add:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Add, Dest, Left, Right));
break;
case kExprF32Add:
case kExprF64Add:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Fadd,
Dest, Left, Right));
break;
case kExprI32Sub:
case kExprI64Sub:
Control()->appendInst(
......@@ -321,6 +406,11 @@ public:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Urem,
Dest, Left, Right));
break;
case kExprI32RemS:
case kExprI64RemS:
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Srem,
Dest, Left, Right));
break;
case kExprI32Ior:
case kExprI64Ior:
Control()->appendInst(
......@@ -336,6 +426,28 @@ public:
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Shl, Dest, Left, Right));
break;
case kExprI32Rol: {
// TODO(eholk): add rotate as an ICE instruction to make it easier to take
// advantage of hardware support.
// TODO(eholk): don't hardcode so many numbers.
auto *Masked = makeVariable(IceType_i32);
auto *Bottom = makeVariable(IceType_i32);
auto *Top = makeVariable(IceType_i32);
Control()->appendInst(InstArithmetic::create(
Func, InstArithmetic::And, Masked, Right, Ctx->getConstantInt32(31)));
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Shl, Top, Left, Masked));
auto *RotShift = makeVariable(IceType_i32);
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Sub, RotShift,
Ctx->getConstantInt32(32), Masked));
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Lshr,
Bottom, Left, Masked));
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Or, Dest, Top, Bottom));
break;
}
case kExprI32ShrU:
case kExprI64ShrU:
case kExprI32ShrS:
......@@ -349,39 +461,112 @@ public:
InstArithmetic::create(Func, InstArithmetic::And, Dest, Left, Right));
break;
case kExprI32Ne:
case kExprI64Ne:
case kExprI64Ne: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ne, TmpDest, Left, Right));
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ne, Dest, Left, Right));
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprI32Eq:
case kExprI64Eq:
case kExprI64Eq: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Eq, Dest, Left, Right));
InstIcmp::create(Func, InstIcmp::Eq, TmpDest, Left, Right));
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprI32LtS:
case kExprI64LtS:
case kExprI64LtS: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Slt, TmpDest, Left, Right));
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprI32LeS:
case kExprI64LeS: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Sle, TmpDest, Left, Right));
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprI32GeU:
case kExprI64GeU: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Uge, TmpDest, Left, Right));
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprI32LeU:
case kExprI64LeU: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ule, TmpDest, Left, Right));
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Slt, Dest, Left, Right));
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprI32LtU:
case kExprI64LtU:
case kExprI64LtU: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ult, TmpDest, Left, Right));
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ult, Dest, Left, Right));
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprI32GeS:
case kExprI64GeS:
case kExprI64GeS: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Sge, Dest, Left, Right));
InstIcmp::create(Func, InstIcmp::Sge, TmpDest, Left, Right));
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
}
case kExprI32GtS:
case kExprI64GtS:
case kExprI64GtS: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Sgt, TmpDest, Left, Right));
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Sgt, Dest, Left, Right));
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprI32GtU:
case kExprI64GtU:
case kExprI64GtU: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ugt, TmpDest, Left, Right));
Control()->appendInst(
InstIcmp::create(Func, InstIcmp::Ugt, Dest, Left, Right));
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprF32Ne:
case kExprF64Ne: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstFcmp::create(Func, InstFcmp::Une, TmpDest, Left, Right));
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
case kExprF32Le:
case kExprF64Le: {
auto *TmpDest = makeVariable(IceType_i1);
Control()->appendInst(
InstFcmp::create(Func, InstFcmp::Ule, TmpDest, Left, Right));
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, TmpDest));
break;
}
default:
LOG(out << "Unknown binop: " << WasmOpcodes::OpcodeName(Opcode) << "\n");
llvm::report_fatal_error("Uncovered or invalid binop.");
......@@ -395,6 +580,14 @@ public:
<< ") = ");
Ice::Variable *Dest = nullptr;
switch (Opcode) {
case kExprI32Eqz: {
Dest = makeVariable(IceType_i32);
auto *Tmp = makeVariable(IceType_i1);
Control()->appendInst(InstIcmp::create(Func, InstIcmp::Eq, Tmp, Input,
Ctx->getConstantInt32(0)));
Control()->appendInst(InstCast::create(Func, InstCast::Sext, Dest, Tmp));
break;
}
case kExprF32Neg: {
Dest = makeVariable(IceType_f32);
Control()->appendInst(InstArithmetic::create(
......@@ -412,6 +605,21 @@ public:
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Dest, Input));
break;
case kExprI64SConvertI32:
Dest = makeVariable(IceType_i64);
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Dest, Input));
break;
case kExprI32ConvertI64:
Dest = makeVariable(IceType_i32);
Control()->appendInst(
InstCast::create(Func, InstCast::Trunc, Dest, Input));
break;
case kExprF64SConvertI32:
Dest = makeVariable(IceType_f64);
Control()->appendInst(
InstCast::create(Func, InstCast::Sitofp, Dest, Input));
break;
default:
LOG(out << "Unknown unop: " << WasmOpcodes::OpcodeName(Opcode) << "\n");
llvm::report_fatal_error("Uncovered or invalid unop.");
......@@ -420,14 +628,14 @@ public:
LOG(out << Dest << "\n");
return OperandNode(Dest);
}
unsigned InputCount(CfgNode *Node) const { return Node->getInEdges().size(); }
uint32_t InputCount(CfgNode *Node) const { return Node->getInEdges().size(); }
bool IsPhiWithMerge(Node Phi, Node Merge) const {
LOG(out << "IsPhiWithMerge(" << Phi << ", " << Merge << ")"
<< "\n");
if (Phi && Phi.isOperand()) {
LOG(out << " ...is operand"
<< "\n");
if (auto *Inst = getDefiningInst(Phi)) {
if (getDefiningInst(Phi)) {
LOG(out << " ...has defining instruction"
<< "\n");
LOG(out << getDefNode(Phi) << "\n");
......@@ -444,6 +652,7 @@ public:
LOG(out << "AppendToPhi(" << Merge << ", " << Phi << ", " << From << ")"
<< "\n");
auto *Inst = getDefiningInst(Phi);
assert(Inst->getDest()->getType() == From.toOperand()->getType());
Inst->addArgument(From, getDefNode(From));
}
......@@ -463,13 +672,50 @@ public:
LOG(out << *TrueNode << ", " << *FalseNode << ")"
<< "\n");
Ctrl->appendInst(InstBr::create(Func, Cond, *TrueNode, *FalseNode));
auto *CondBool = makeVariable(IceType_i1);
Ctrl->appendInst(InstCast::create(Func, InstCast::Trunc, CondBool, Cond));
Ctrl->appendInst(InstBr::create(Func, CondBool, *TrueNode, *FalseNode));
return OperandNode(nullptr);
}
Node Switch(unsigned Count, Node Key) { llvm::report_fatal_error("Switch"); }
Node IfValue(int32_t Value, Node Sw) { llvm::report_fatal_error("IfValue"); }
Node IfDefault(Node Sw) { llvm::report_fatal_error("IfDefault"); }
Node Return(unsigned Count, Node *Vals) {
InstSwitch *CurrentSwitch = nullptr;
CfgNode *SwitchNode = nullptr;
SizeT SwitchIndex = 0;
Node Switch(uint32_t Count, Node Key) {
LOG(out << "Switch(" << Count << ", " << Key << ")\n");
assert(!CurrentSwitch);
auto *Default = Func->makeNode();
// Count - 1 because the decoder counts the default label but Subzero does
// not.
CurrentSwitch = InstSwitch::create(Func, Count - 1, Key, Default);
SwitchIndex = 0;
SwitchNode = Control();
// We don't actually append the switch to the CfgNode here because not all
// the branches are ready.
return Node(nullptr);
}
Node IfValue(int32_t Value, Node) {
LOG(out << "IfValue(" << Value << ") [Index = " << SwitchIndex << "]\n");
assert(CurrentSwitch);
auto *Target = Func->makeNode();
CurrentSwitch->addBranch(SwitchIndex++, Value, Target);
return Node(Target);
}
Node IfDefault(Node) {
LOG(out << "IfDefault(...) [Index = " << SwitchIndex << "]\n");
assert(CurrentSwitch);
assert(CurrentSwitch->getLabelDefault());
// Now we append the switch, since this should be the last edge.
assert(SwitchIndex == CurrentSwitch->getNumCases());
SwitchNode->appendInst(CurrentSwitch);
SwitchNode = nullptr;
auto Default = Node(CurrentSwitch->getLabelDefault());
CurrentSwitch = nullptr;
return Default;
}
Node Return(uint32_t Count, Node *Vals) {
assert(1 >= Count);
LOG(out << "Return(");
if (Count > 0)
......@@ -511,20 +757,20 @@ public:
const auto NumArgs = Sig->parameter_count();
LOG(out << " number of args: " << NumArgs << "\n");
const auto TargetName =
Ctx->getGlobalString(Module->GetName(Target.name_offset));
const auto TargetName = getFunctionName(Module, Index);
LOG(out << " target name: " << TargetName << "\n");
assert(Sig->return_count() <= 1);
auto *TargetOperand = Ctx->getConstantSym(0, TargetName);
auto TargetOperand =
Ctx->getConstantSym(0, Ctx->getGlobalString(TargetName));
auto *Dest = Sig->return_count() > 0
? makeVariable(toIceType(Sig->GetReturn()))
: nullptr;
auto *Call = InstCall::create(Func, NumArgs, Dest, TargetOperand,
false /* HasTailCall */);
for (int i = 0; i < NumArgs; ++i) {
for (uint32_t i = 0; i < NumArgs; ++i) {
// The builder reserves the first argument for the code object.
LOG(out << " args[" << i << "] = " << Args[i + 1] << "\n");
Call->addArg(Args[i + 1]);
......@@ -545,13 +791,17 @@ public:
LOG(out << " number of args: " << NumArgs << "\n");
const auto &Target = Module->import_table[Index];
const auto TargetName =
Ctx->getGlobalString(Module->GetName(Target.function_name_offset));
const auto ModuleName = toStdString(
Module->GetName(Target.module_name_offset, Target.module_name_length));
const auto FnName = toStdString(Module->GetName(
Target.function_name_offset, Target.function_name_length));
const auto TargetName = Ctx->getGlobalString(ModuleName + "$$" + FnName);
LOG(out << " target name: " << TargetName << "\n");
assert(Sig->return_count() <= 1);
auto *TargetOperand = Ctx->getConstantSym(0, TargetName);
auto TargetOperand = Ctx->getConstantExternSym(TargetName);
auto *Dest = Sig->return_count() > 0
? makeVariable(toIceType(Sig->GetReturn()))
......@@ -559,9 +809,10 @@ public:
constexpr bool NoTailCall = false;
auto *Call =
InstCall::create(Func, NumArgs, Dest, TargetOperand, NoTailCall);
for (int i = 0; i < NumArgs; ++i) {
for (uint32_t i = 0; i < NumArgs; ++i) {
// The builder reserves the first argument for the code object.
LOG(out << " args[" << i << "] = " << Args[i + 1] << "\n");
assert(Args[i + 1].toOperand()->getType() == toIceType(Sig->GetParam(i)));
Call->addArg(Args[i + 1]);
}
......@@ -569,41 +820,140 @@ public:
LOG(out << "Call Result = " << Node(Dest) << "\n");
return OperandNode(Dest);
}
Node CallIndirect(uint32_t Index, Node *Args) {
llvm::report_fatal_error("CallIndirect");
Node CallIndirect(uint32_t SigIndex, Node *Args) {
LOG(out << "CallIndirect(" << SigIndex << ")\n");
// TODO(eholk): Compile to something better than a switch.
const auto *Module = this->Module->module;
assert(Module);
const auto &IndirectTable = Module->function_table;
// TODO(eholk): This should probably actually call abort instead.
auto *Abort = Func->makeNode();
Abort->appendInst(InstUnreachable::create(Func));
assert(Args[0].toOperand());
auto *Switch = InstSwitch::create(Func, IndirectTable.size(),
Args[0].toOperand(), Abort);
assert(Abort);
const bool HasReturn = Module->signatures[SigIndex]->return_count() != 0;
const Ice::Type DestTy =
HasReturn ? toIceType(Module->signatures[SigIndex]->GetReturn())
: IceType_void;
auto *Dest = HasReturn ? makeVariable(DestTy) : nullptr;
auto *ExitNode = Func->makeNode();
auto *PhiInst =
HasReturn ? InstPhi::create(Func, IndirectTable.size(), Dest) : nullptr;
for (uint32_t Index = 0; Index < IndirectTable.size(); ++Index) {
const auto &Target = Module->functions[IndirectTable[Index]];
if (SigIndex == Target.sig_index) {
auto *CallNode = Func->makeNode();
auto *SavedControl = Control();
*ControlPtr = OperandNode(CallNode);
auto *Tmp = CallDirect(Target.func_index, Args).toOperand();
*ControlPtr = OperandNode(SavedControl);
if (PhiInst) {
PhiInst->addArgument(Tmp, CallNode);
}
CallNode->appendInst(InstBr::create(Func, ExitNode));
Switch->addBranch(Index, Index, CallNode);
} else {
Switch->addBranch(Index, Index, Abort);
}
}
if (PhiInst) {
ExitNode->appendInst(PhiInst);
}
Control()->appendInst(Switch);
*ControlPtr = OperandNode(ExitNode);
return OperandNode(Dest);
}
Node Invert(Node Node) {
(void)Node;
llvm::report_fatal_error("Invert");
}
Node Invert(Node Node) { llvm::report_fatal_error("Invert"); }
Node FunctionTable() { llvm::report_fatal_error("FunctionTable"); }
//-----------------------------------------------------------------------
// Operations that concern the linear memory.
//-----------------------------------------------------------------------
Node MemSize(uint32_t Offset) { llvm::report_fatal_error("MemSize"); }
Node LoadGlobal(uint32_t Index) { llvm::report_fatal_error("LoadGlobal"); }
Node MemSize(uint32_t Offset) {
(void)Offset;
llvm::report_fatal_error("MemSize");
}
Node LoadGlobal(uint32_t Index) {
(void)Index;
llvm::report_fatal_error("LoadGlobal");
}
Node StoreGlobal(uint32_t Index, Node Val) {
(void)Index;
(void)Val;
llvm::report_fatal_error("StoreGlobal");
}
Operand *sanitizeAddress(Operand *Base, uint32_t Offset) {
// first, add the index and the offset together.
if (0 != Offset) {
auto *Addr = makeVariable(IceType_i32);
auto *OffsetConstant = Ctx->getConstantInt32(Offset);
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Add,
Addr, Base, OffsetConstant));
Base = Addr;
}
SizeT MemSize = Module->module->min_mem_pages * (16 << 10);
auto *WrappedAddr = makeVariable(IceType_i32);
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::Add, WrappedAddr, Base,
Ctx->getConstantInt32(MemSize)));
auto ClampedAddr = makeVariable(IceType_i32);
Control()->appendInst(
InstArithmetic::create(Func, InstArithmetic::And, ClampedAddr, Base,
Ctx->getConstantInt32(MemSize - 1)));
auto RealAddr = Func->makeVariable(IceType_i32);
auto MemBase = Ctx->getConstantSym(0, Ctx->getGlobalString("WASM_MEMORY"));
Control()->appendInst(InstArithmetic::create(
Func, InstArithmetic::Add, RealAddr, ClampedAddr, MemBase));
return RealAddr;
}
Node LoadMem(wasm::LocalType Type, MachineType MemType, Node Index,
uint32_t Offset) {
LOG(out << "LoadMem(" << Index << "[" << Offset << "]) = ");
// first, add the index and the offset together.
auto *OffsetConstant = Ctx->getConstantInt32(Offset);
auto *Addr = makeVariable(IceType_i32);
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Add,
Addr, Index, OffsetConstant));
auto *RealAddr = sanitizeAddress(Index, Offset);
// then load the memory
auto *LoadResult = makeVariable(toIceType(MemType));
Control()->appendInst(InstLoad::create(Func, LoadResult, Addr));
Control()->appendInst(InstLoad::create(Func, LoadResult, RealAddr));
// and cast, if needed
Ice::Variable *Result = nullptr;
if (toIceType(Type) != toIceType(MemType)) {
Result = makeVariable(toIceType(Type));
auto DestType = toIceType(Type);
Result = makeVariable(DestType);
// TODO(eholk): handle signs correctly.
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Result, LoadResult));
if (isScalarIntegerType(DestType)) {
if (MemType.IsSigned()) {
Control()->appendInst(
InstCast::create(Func, InstCast::Sext, Result, LoadResult));
} else {
Control()->appendInst(
InstCast::create(Func, InstCast::Zext, Result, LoadResult));
}
} else if (isScalarFloatingType(DestType)) {
Control()->appendInst(
InstCast::create(Func, InstCast::Sitofp, Result, LoadResult));
} else {
llvm::report_fatal_error("Unsupported type for memory load");
}
} else {
Result = LoadResult;
}
......@@ -615,13 +965,7 @@ public:
LOG(out << "StoreMem(" << Index << "[" << Offset << "] = " << Val << ")"
<< "\n");
// TODO(eholk): surely there is a better way to do this.
// first, add the index and the offset together.
auto *OffsetConstant = Ctx->getConstantInt32(Offset);
auto *Addr = makeVariable(IceType_i32);
Control()->appendInst(InstArithmetic::create(Func, InstArithmetic::Add,
Addr, Index, OffsetConstant));
auto *RealAddr = sanitizeAddress(Index, Offset);
// cast the value to the right type, if needed
Operand *StoreVal = nullptr;
......@@ -635,10 +979,11 @@ public:
}
// then store the memory
Control()->appendInst(InstStore::create(Func, StoreVal, Addr));
Control()->appendInst(InstStore::create(Func, StoreVal, RealAddr));
}
static void PrintDebugName(Node node) {
static void PrintDebugName(OperandNode Node) {
(void)Node;
llvm::report_fatal_error("PrintDebugName");
}
......@@ -705,36 +1050,33 @@ private:
}
};
std::string fnNameFromId(uint32_t Id) {
return std::string("fn") + to_string(Id);
}
std::unique_ptr<Cfg> WasmTranslator::translateFunction(Zone *Zone,
FunctionEnv *Env,
const byte *Base,
const byte *Start,
const byte *End) {
FunctionBody &Body) {
OstreamLocker L1(Ctx);
auto Func = Cfg::create(Ctx, getNextSequenceNumber());
Ice::CfgLocalAllocatorScope L2(Func.get());
// TODO: parse the function signature...
// TODO(eholk): parse the function signature...
Func->setEntryNode(Func->makeNode());
IceBuilder Builder(Func.get());
LR_WasmDecoder<OperandNode, IceBuilder> Decoder(Zone, &Builder);
SR_WasmDecoder<OperandNode, IceBuilder> Decoder(Zone, &Builder, Body);
LOG(out << getFlags().getDefaultGlobalPrefix() << "\n");
Decoder.Decode(Env, Base, Start, End);
Decoder.Decode();
// We don't always know where the incoming branches are in phi nodes, so this
// function finds them.
Func->fixPhiNodes();
Func->computeInOutEdges();
return Func;
}
WasmTranslator::WasmTranslator(GlobalContext *Ctx)
: Translator(Ctx), BufferSize(24 << 10), Buffer(new uint8_t[24 << 10]) {
: Translator(Ctx), Buffer(new uint8_t[24 << 10]), BufferSize(24 << 10) {
// TODO(eholk): compute the correct buffer size. This uses 24k by default,
// which has been big enough for testing but is not a general solution.
}
......@@ -761,6 +1103,8 @@ void WasmTranslator::translate(
LOG(out << "Module info:"
<< "\n");
LOG(out << " min_mem_pages: " << Module->min_mem_pages << "\n");
LOG(out << " max_mem_pages: " << Module->max_mem_pages << "\n");
LOG(out << " number of globals: " << Module->globals.size() << "\n");
LOG(out << " number of signatures: " << Module->signatures.size()
<< "\n");
......@@ -769,15 +1113,56 @@ void WasmTranslator::translate(
<< "\n");
LOG(out << " function table size: " << Module->function_table.size()
<< "\n");
LOG(out << " import table size: " << Module->import_table.size()
<< "\n");
LOG(out << " export table size: " << Module->export_table.size()
<< "\n");
ModuleEnv ModuleEnv;
ModuleEnv.module = Module;
LOG(out << "\n"
<< "Data segment information:"
<< "\n");
uint32_t Id = 0;
for (const auto Seg : Module->data_segments) {
LOG(out << Id << ": (" << Seg.source_offset << ", " << Seg.source_size
<< ") => " << Seg.dest_addr);
if (Seg.init) {
LOG(out << " init\n");
} else {
LOG(out << "\n");
}
Id++;
}
LOG(out << "\n"
<< "Import information:"
<< "\n");
for (const auto Import : Module->import_table) {
auto ModuleName = toStdString(
Module->GetName(Import.module_name_offset, Import.module_name_length));
auto FnName = toStdString(Module->GetName(Import.function_name_offset,
Import.function_name_length));
LOG(out << " " << Import.sig_index << ": " << ModuleName << "::" << FnName
<< "\n");
}
LOG(out << "\n"
<< "Export information:"
<< "\n");
for (const auto Export : Module->export_table) {
LOG(out << " " << Export.func_index << ": "
<< toStdString(
Module->GetName(Export.name_offset, Export.name_length))
<< " (" << Export.name_offset << ", " << Export.name_length << ")");
LOG(out << "\n");
}
LOG(out << "\n"
<< "Function information:"
<< "\n");
for (const auto F : Module->functions) {
LOG(out << " " << F.name_offset << ": " << Module->GetName(F.name_offset));
LOG(out << " " << F.func_index << ": "
<< toStdString(Module->GetName(F.name_offset, F.name_length))
<< " (" << F.name_offset << ", " << F.name_length << ")");
if (F.exported)
LOG(out << " export");
if (F.external)
......@@ -785,29 +1170,76 @@ void WasmTranslator::translate(
LOG(out << "\n");
}
FunctionEnv Fenv;
Fenv.module = &ModuleEnv;
LOG(out << "\n"
<< "Indirect table:"
<< "\n");
for (uint32_t F : Module->function_table) {
LOG(out << " " << F << ": " << getFunctionName(Module, F) << "\n");
}
ModuleEnv ModuleEnv;
ModuleEnv.module = Module;
FunctionBody Body;
Body.module = &ModuleEnv;
LOG(out << "Translating " << IRFilename << "\n");
{
unique_ptr<VariableDeclarationList> Globals =
makeUnique<VariableDeclarationList>();
// Global variables, etc go here.
auto *WasmMemory = VariableDeclaration::createExternal(Globals.get());
WasmMemory->setName(Ctx->getGlobalString("WASM_MEMORY"));
// Fill in the segments
SizeT WritePtr = 0;
for (const auto Seg : Module->data_segments) {
// fill in gaps with zero.
if (Seg.dest_addr > WritePtr) {
WasmMemory->addInitializer(VariableDeclaration::ZeroInitializer::create(
Globals.get(), Seg.dest_addr - WritePtr));
WritePtr = Seg.dest_addr;
}
// Add the data
WasmMemory->addInitializer(VariableDeclaration::DataInitializer::create(
Globals.get(), reinterpret_cast<const char *>(Module->module_start) +
Seg.source_offset,
Seg.source_size));
WritePtr += Seg.source_size;
}
// Pad the rest with zeros
SizeT DataSize = Module->min_mem_pages * (64 << 10);
if (WritePtr < DataSize) {
WasmMemory->addInitializer(VariableDeclaration::ZeroInitializer::create(
Globals.get(), DataSize - WritePtr));
}
WasmMemory->addInitializer(VariableDeclaration::ZeroInitializer::create(
Globals.get(), Module->min_mem_pages * (64 << 10)));
Globals->push_back(WasmMemory);
lowerGlobals(std::move(Globals));
}
// Translate each function.
uint32_t Id = 0;
for (const auto Fn : Module->functions) {
std::string NewName = fnNameFromId(Id++);
LOG(out << " " << Fn.name_offset << ": " << Module->GetName(Fn.name_offset)
<< " -> " << NewName << "...");
Fenv.sig = Fn.sig;
Fenv.local_i32_count = Fn.local_i32_count;
Fenv.local_i64_count = Fn.local_i64_count;
Fenv.local_f32_count = Fn.local_f32_count;
Fenv.local_f64_count = Fn.local_f64_count;
Fenv.SumLocals();
auto Func = translateFunction(&Zone, &Fenv, Buffer.get(),
Buffer.get() + Fn.code_start_offset,
Buffer.get() + Fn.code_end_offset);
Func->setFunctionName(Ctx->getGlobalString(NewName));
const auto FnName = getFunctionName(Module, Fn.func_index);
LOG(out << " " << Fn.func_index << ": " << FnName << "...");
Body.sig = Fn.sig;
Body.base = Buffer.get();
Body.start = Buffer.get() + Fn.code_start_offset;
Body.end = Buffer.get() + Fn.code_end_offset;
auto Func = translateFunction(&Zone, Body);
Func->setFunctionName(Ctx->getGlobalString(FnName));
Ctx->optQueueBlockingPush(makeUnique<CfgOptWorkItem>(std::move(Func)));
LOG(out << "done.\n");
......
......@@ -20,11 +20,22 @@
#include "IceGlobalContext.h"
#include "IceTranslator.h"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif // __clang__
#include "llvm/Support/StreamingMemoryObject.h"
#ifdef __clang__
#pragma clang diagnostic pop
#endif // __clang__
namespace v8 {
namespace internal {
class Zone;
namespace wasm {
class FunctionEnv;
struct FunctionBody;
} // end of namespace wasm
} // end of namespace internal
} // end of namespace v8
......@@ -53,15 +64,10 @@ public:
///
/// Parameters:
/// Zone - an arena for the V8 code to allocate from.
/// Env - information about the function (signature, variable count, etc.).
/// Base - a pointer to the start of the Wasm module.
/// Start - a pointer to the start of the function within the module.
/// End - a pointer to the end of the function.
std::unique_ptr<Cfg> translateFunction(v8::internal::Zone *Zone,
v8::internal::wasm::FunctionEnv *Env,
const uint8_t *Base,
const uint8_t *Start,
const uint8_t *End);
/// Body - information about the function to translate
std::unique_ptr<Cfg>
translateFunction(v8::internal::Zone *Zone,
v8::internal::wasm::FunctionBody &Body);
private:
std::unique_ptr<uint8_t[]> Buffer;
......
#include <stdio.h>
int main(int argc, const char **argv) {
printf("Hello, World!\n");
return 0;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void write_int_(int fd, int n) {
if (n > 0) {
write_int_(fd, n / 10);
int rem = n % 10;
char c = '0' + rem;
write(fd, &c, 1);
}
}
void write_int(int fd, int n) {
if (n == 0) {
write(fd, "0", 1);
} else {
if (n < 0) {
write(fd, "-", 1);
write_int_(fd, -n);
} else {
write_int_(fd, n);
}
}
}
void stderr_int(int n) {
write_int(2, n);
write(2, "\n", 1);
}
int main(int argc, const char **argv) {
char *str = "Hello, World!\n";
for (int i = 0; str[i]; ++i) {
putchar(str[i]);
}
return 0;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char **argv) {
fputs("Hello,", stdout);
fputs(" ", stdout);
fputs("world\n", stdout);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char **argv) {
char *str = "Hello, World!\n";
const int len = strlen(str);
write(1, str, len);
return 0;
}
int foo() { return 5; }
int bar() { return 6; }
int baz() { return 7; }
int (*TABLE[])() = {foo, baz, bar, baz};
int main(int argc, const char **argv) {
int (*f)() = TABLE[argc - 1];
return f();
}
// This is derived from the loop in musl's __fwritex that looks for newlines.
int puts(const char *s);
int main(int argc, const char **argv) {
const char *p = (const char *)argv;
char *s = "Hello\nWorld";
unsigned i = 0;
// Depend on argc to avoid having this whole thing get dead-code-eliminated.
for (i = 14 - argc; i && p[i - 1] != '\n'; i--)
;
puts(s);
return i;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment