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
......
......@@ -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