Commit 0a9d4003 authored by ddchen's avatar ddchen Committed by Commit bot

[wasm] Add support for multiple indirect function tables

This patch updates internal data structures used by V8 to support
multiple indirect function tables (WebAssembly/design#682). But, since
this feature is post-MVP, the functionality is not directly exposed and
parsing/generation of WebAssembly is left unchanged. Nevertheless, it
is being used in an experiment to implement fine-grained control flow
integrity based on C/C++ types.

BUG=

Review-Url: https://codereview.chromium.org/2174123002
Cr-Commit-Position: refs/heads/master@{#38110}
parent ff683fed
......@@ -278,7 +278,7 @@ WasmGraphBuilder::WasmGraphBuilder(
module_(nullptr),
mem_buffer_(nullptr),
mem_size_(nullptr),
function_table_(nullptr),
function_tables_(zone),
control_(nullptr),
effect_(nullptr),
cur_buffer_(def_buffer_),
......@@ -2059,11 +2059,14 @@ Node* WasmGraphBuilder::CallIndirect(uint32_t index, Node** args,
// Compute the code object by loading it from the function table.
Node* key = args[0];
// Assume only one table for now.
DCHECK_LE(module_->instance->function_tables.size(), 1u);
// Bounds check the index.
int table_size = static_cast<int>(module_->FunctionTableSize());
uint32_t table_size =
module_->IsValidTable(0) ? module_->GetTable(0)->max_size : 0;
if (table_size > 0) {
// Bounds check against the table size.
Node* size = Int32Constant(static_cast<int>(table_size));
Node* size = Uint32Constant(table_size);
Node* in_bounds = graph()->NewNode(machine->Uint32LessThan(), key, size);
trap_->AddTrapIfFalse(wasm::kTrapFuncInvalid, in_bounds, position);
} else {
......@@ -2071,7 +2074,7 @@ Node* WasmGraphBuilder::CallIndirect(uint32_t index, Node** args,
trap_->AddTrapIfFalse(wasm::kTrapFuncInvalid, Int32Constant(0), position);
return trap_->GetTrapValue(module_->GetSignature(index));
}
Node* table = FunctionTable();
Node* table = FunctionTable(0);
// Load signature from the table and check.
// The table is a FixedArray; signatures are encoded as SMIs.
......@@ -2093,13 +2096,13 @@ Node* WasmGraphBuilder::CallIndirect(uint32_t index, Node** args,
}
// Load code object from the table.
int offset = fixed_offset + kPointerSize * table_size;
uint32_t offset = fixed_offset + kPointerSize * table_size;
Node* load_code = graph()->NewNode(
machine->Load(MachineType::AnyTagged()), table,
graph()->NewNode(machine->Int32Add(),
graph()->NewNode(machine->Word32Shl(), key,
Int32Constant(kPointerSizeLog2)),
Int32Constant(offset)),
Uint32Constant(offset)),
*effect_, *control_);
args[0] = load_code;
......@@ -2131,10 +2134,13 @@ Node* WasmGraphBuilder::JITSingleFunction(Node* const base, Node* const length,
// Bounds check the index.
{
int table_size = static_cast<int>(module_->FunctionTableSize());
// Assume only one table for now.
DCHECK_LE(module_->instance->function_tables.size(), 1u);
uint32_t table_size =
module_->IsValidTable(0) ? module_->GetTable(0)->max_size : 0;
if (table_size > 0) {
// Bounds check against the table size.
Node* size = Int32Constant(static_cast<int>(table_size));
Node* size = Uint32Constant(table_size);
Node* in_bounds =
graph()->NewNode(machine->Uint32LessThan(), index, size);
trap_->AddTrapIfFalse(wasm::kTrapInvalidIndex, in_bounds, position);
......@@ -2164,7 +2170,7 @@ Node* WasmGraphBuilder::JITSingleFunction(Node* const base, Node* const length,
inputs[1] = BuildChangeUint32ToSmi(base);
inputs[2] = BuildChangeUint32ToSmi(length);
inputs[3] = BuildChangeUint32ToSmi(index);
inputs[4] = FunctionTable();
inputs[4] = FunctionTable(0);
inputs[5] = Uint32Constant(sig_index);
inputs[6] = BuildChangeUint32ToSmi(Uint32Constant(return_count));
......@@ -2806,13 +2812,17 @@ Node* WasmGraphBuilder::DefaultS128Value() {
zero, zero);
}
Node* WasmGraphBuilder::FunctionTable() {
Node* WasmGraphBuilder::FunctionTable(uint32_t index) {
DCHECK(module_ && module_->instance &&
!module_->instance->function_table.is_null());
if (!function_table_) {
function_table_ = HeapConstant(module_->instance->function_table);
index < module_->instance->function_tables.size());
if (!function_tables_.size()) {
for (size_t i = 0; i < module_->instance->function_tables.size(); ++i) {
DCHECK(!module_->instance->function_tables[i].is_null());
function_tables_.push_back(
HeapConstant(module_->instance->function_tables[i]));
}
}
return function_table_;
return function_tables_[index];
}
Node* WasmGraphBuilder::ChangeToRuntimeCall(Node* node,
......
......@@ -168,7 +168,7 @@ class WasmGraphBuilder {
Node* ToJS(Node* node, Node* context, wasm::LocalType type);
Node* FromJS(Node* node, Node* context, wasm::LocalType type);
Node* Invert(Node* node);
Node* FunctionTable();
Node* FunctionTable(uint32_t index);
Node* ChangeToRuntimeCall(Node* node, Runtime::FunctionId function_id,
Signature<Conversion>* signature);
......@@ -215,7 +215,7 @@ class WasmGraphBuilder {
wasm::ModuleEnv* module_;
Node* mem_buffer_;
Node* mem_size_;
Node* function_table_;
NodeVector function_tables_;
Node** control_;
Node** effect_;
Node** cur_buffer_;
......
......@@ -257,38 +257,19 @@ class ModuleDecoder : public Decoder {
}
break;
}
case WasmSection::Code::FunctionTablePad: {
if (!FLAG_wasm_jit_prototype) {
error("FunctionTablePad section without jiting enabled");
}
// An indirect function table requires functions first.
module->indirect_table_size = consume_u32v("indirect entry count");
if (module->indirect_table_size > 0 &&
module->indirect_table_size < module->function_table.size()) {
error("more predefined indirect entries than table can hold");
}
break;
}
case WasmSection::Code::FunctionTable: {
// An indirect function table requires functions first.
CheckForFunctions(module, section);
uint32_t function_table_count = consume_u32v("function table count");
module->function_table.reserve(SafeReserve(function_table_count));
// Assume only one table for now.
static const uint32_t kSupportedTableCount = 1;
module->function_tables.reserve(SafeReserve(kSupportedTableCount));
// Decode function table.
for (uint32_t i = 0; i < function_table_count; ++i) {
for (uint32_t i = 0; i < kSupportedTableCount; ++i) {
if (failed()) break;
TRACE("DecodeFunctionTable[%d] module+%d\n", i,
static_cast<int>(pc_ - start_));
uint16_t index = consume_u32v();
if (index >= module->functions.size()) {
error(pc_ - 2, "invalid function index");
break;
}
module->function_table.push_back(index);
}
if (module->indirect_table_size > 0 &&
module->indirect_table_size < module->function_table.size()) {
error("more predefined indirect entries than table can hold");
module->function_tables.push_back({0, 0, std::vector<uint16_t>()});
DecodeFunctionTableInModule(module, &module->function_tables[i]);
}
break;
}
......@@ -520,6 +501,27 @@ class ModuleDecoder : public Decoder {
consume_bytes(segment->source_size);
}
// Decodes a single function table inside a module starting at {pc_}.
void DecodeFunctionTableInModule(WasmModule* module,
WasmIndirectFunctionTable* table) {
table->size = consume_u32v("function table entry count");
table->max_size = FLAG_wasm_jit_prototype ? consume_u32v() : table->size;
if ((!FLAG_wasm_jit_prototype && table->max_size != table->size) ||
(FLAG_wasm_jit_prototype && table->max_size < table->size)) {
error("invalid table maximum size");
}
for (uint32_t i = 0; i < table->size; ++i) {
uint16_t index = consume_u32v();
if (index >= module->functions.size()) {
error(pc_ - sizeof(index), "invalid function index");
break;
}
table->values.push_back(index);
}
}
// Calculate individual global offsets and total size of globals table.
void CalculateGlobalsOffsets(WasmModule* module) {
uint32_t offset = 0;
......
......@@ -915,9 +915,12 @@ class CodeMap {
return Preprocess(&interpreter_code_[function_index]);
}
InterpreterCode* GetIndirectCode(uint32_t indirect_index) {
if (indirect_index >= module_->function_table.size()) return nullptr;
uint32_t index = module_->function_table[indirect_index];
InterpreterCode* GetIndirectCode(uint32_t table_index, uint32_t entry_index) {
if (table_index >= module_->function_tables.size()) return nullptr;
const WasmIndirectFunctionTable* table =
&module_->function_tables[table_index];
if (entry_index >= table->values.size()) return nullptr;
uint32_t index = table->values[entry_index];
if (index >= interpreter_code_.size()) return nullptr;
return GetCode(index);
}
......@@ -1383,14 +1386,13 @@ class ThreadImpl : public WasmInterpreter::Thread {
CallIndirectOperand operand(&decoder, code->at(pc));
size_t index = stack_.size() - operand.arity - 1;
DCHECK_LT(index, stack_.size());
uint32_t table_index = stack_[index].to<uint32_t>();
if (table_index >= module()->function_table.size()) {
uint32_t entry_index = stack_[index].to<uint32_t>();
// Assume only one table for now.
DCHECK_LE(module()->function_tables.size(), 1u);
InterpreterCode* target = codemap()->GetIndirectCode(0, entry_index);
if (target == nullptr) {
return DoTrap(kTrapFuncInvalid, pc);
}
uint16_t function_index = module()->function_table[table_index];
InterpreterCode* target = codemap()->GetCode(function_index);
DCHECK(target);
if (target->function->sig_index != operand.index) {
} else if (target->function->sig_index != operand.index) {
return DoTrap(kTrapFuncSigMismatch, pc);
}
......
This diff is collapsed.
......@@ -43,7 +43,6 @@ const uint8_t kWasmFunctionTypeForm = 0x40;
F(FunctionBodies, 8, "code") \
F(DataSegments, 9, "data") \
F(Names, 10, "name") \
F(FunctionTablePad, 11, "table_pad") \
F(Globals, 0, "global") \
F(End, 0, "end")
......@@ -61,8 +60,6 @@ const uint8_t kWasmFunctionTypeForm = 0x40;
8, 'f', 'u', 'n', 'c', 't', 'i', 'o', 'n'
#define WASM_SECTION_FUNCTION_BODIES 4, 'c', 'o', 'd', 'e'
#define WASM_SECTION_NAMES 4, 'n', 'a', 'm', 'e'
#define WASM_SECTION_FUNCTION_TABLE_PAD \
9, 't', 'a', 'b', 'l', 'e', '_', 'p', 'a', 'd'
// Constants for the above section headers' size (LEB128 + characters).
#define WASM_SECTION_MEMORY_SIZE ((size_t)7)
......@@ -77,7 +74,6 @@ const uint8_t kWasmFunctionTypeForm = 0x40;
#define WASM_SECTION_FUNCTION_SIGNATURES_SIZE ((size_t)9)
#define WASM_SECTION_FUNCTION_BODIES_SIZE ((size_t)5)
#define WASM_SECTION_NAMES_SIZE ((size_t)5)
#define WASM_SECTION_FUNCTION_TABLE_PAD_SIZE ((size_t)10)
class WasmDebugInfo;
......@@ -153,6 +149,13 @@ struct WasmDataSegment {
bool init; // true if loaded upon instantiation.
};
// Static representation of a wasm indirect call table.
struct WasmIndirectFunctionTable {
uint32_t size; // initial table size.
uint32_t max_size; // maximum table size.
std::vector<uint16_t> values; // function table.
};
enum ModuleOrigin { kWasmOrigin, kAsmJsOrigin };
// Static representation of a module.
......@@ -175,12 +178,10 @@ struct WasmModule {
std::vector<WasmGlobal> globals; // globals in this module.
uint32_t globals_size; // size of globals table.
uint32_t indirect_table_size; // size of indirect function
// table (includes padding).
std::vector<FunctionSig*> signatures; // signatures in this module.
std::vector<WasmFunction> functions; // functions in this module.
std::vector<WasmDataSegment> data_segments; // data segments in this module.
std::vector<uint16_t> function_table; // function table.
std::vector<WasmIndirectFunctionTable> function_tables; // function tables.
std::vector<WasmImport> import_table; // import table.
std::vector<WasmExport> export_table; // export table.
// We store the semaphore here to extend its lifetime. In <libc-2.21, which we
......@@ -238,14 +239,6 @@ struct WasmModule {
MaybeHandle<FixedArray> CompileFunctions(Isolate* isolate,
ErrorThrower* thrower) const;
uint32_t FunctionTableSize() const {
if (indirect_table_size > 0) {
return indirect_table_size;
}
DCHECK_LE(function_table.size(), UINT32_MAX);
return static_cast<uint32_t>(function_table.size());
}
private:
DISALLOW_COPY_AND_ASSIGN(WasmModule);
};
......@@ -258,7 +251,7 @@ struct WasmModuleInstance {
Handle<Context> context; // JavaScript native context.
Handle<JSArrayBuffer> mem_buffer; // Handle to array buffer of memory.
Handle<JSArrayBuffer> globals_buffer; // Handle to array buffer of globals.
Handle<FixedArray> function_table; // indirect function table.
std::vector<Handle<FixedArray>> function_tables; // indirect function tables.
std::vector<Handle<Code>> function_code; // code objects for each function.
std::vector<Handle<Code>> import_code; // code objects for each import.
// -- raw memory ------------------------------------------------------------
......@@ -269,6 +262,7 @@ struct WasmModuleInstance {
explicit WasmModuleInstance(const WasmModule* m)
: module(m),
function_tables(m->function_tables.size()),
function_code(m->functions.size()),
import_code(m->import_table.size()),
mem_start(nullptr),
......@@ -286,18 +280,21 @@ struct ModuleEnv {
// reloc infos.
std::vector<Handle<Code>> placeholders;
bool IsValidGlobal(uint32_t index) {
bool IsValidGlobal(uint32_t index) const {
return module && index < module->globals.size();
}
bool IsValidFunction(uint32_t index) const {
return module && index < module->functions.size();
}
bool IsValidSignature(uint32_t index) {
bool IsValidSignature(uint32_t index) const {
return module && index < module->signatures.size();
}
bool IsValidImport(uint32_t index) {
bool IsValidImport(uint32_t index) const {
return module && index < module->import_table.size();
}
bool IsValidTable(uint32_t index) const {
return module && index < module->function_tables.size();
}
LocalType GetGlobalType(uint32_t index) {
DCHECK(IsValidGlobal(index));
return module->globals[index].type;
......@@ -314,8 +311,9 @@ struct ModuleEnv {
DCHECK(IsValidSignature(index));
return module->signatures[index];
}
uint32_t FunctionTableSize() const {
return module->FunctionTableSize();
const WasmIndirectFunctionTable* GetTable(uint32_t index) const {
DCHECK(IsValidTable(index));
return &module->function_tables[index];
}
bool asm_js() { return origin == kAsmJsOrigin; }
......@@ -387,6 +385,16 @@ bool UpdateWasmModuleMemory(Handle<JSObject> object, Address old_start,
Address new_start, uint32_t old_size,
uint32_t new_size);
// Constructs a single function table as a FixedArray of double size,
// populating it with function signature indices and function indices.
Handle<FixedArray> BuildFunctionTable(Isolate* isolate, uint32_t index,
const WasmModule* module);
// Populates a function table by replacing function indices with handles to
// the compiled code.
void PopulateFunctionTable(Handle<FixedArray> table, uint32_t table_size,
const std::vector<Handle<Code>>* code_table);
namespace testing {
// Decode, verify, and run the function labeled "main" in the
......
......@@ -2407,8 +2407,9 @@ WASM_EXEC_TEST(SimpleCallIndirect) {
module.AddSignature(sigs.d_dd());
// Function table.
int table[] = {0, 1};
module.AddIndirectFunctionTable(table, 2);
uint16_t indirect_function_table[] = {0, 1};
module.AddIndirectFunctionTable(indirect_function_table,
arraysize(indirect_function_table));
module.PopulateIndirectFunctionTable();
// Builder the caller function.
......@@ -2438,8 +2439,9 @@ WASM_EXEC_TEST(MultipleCallIndirect) {
module.AddSignature(sigs.d_dd());
// Function table.
int table[] = {0, 1};
module.AddIndirectFunctionTable(table, 2);
uint16_t indirect_function_table[] = {0, 1};
module.AddIndirectFunctionTable(indirect_function_table,
arraysize(indirect_function_table));
module.PopulateIndirectFunctionTable();
// Builder the caller function.
......
......@@ -222,25 +222,24 @@ class TestingModule : public ModuleEnv {
instance->function_code[index] = code;
}
void AddIndirectFunctionTable(int* functions, int table_size) {
Handle<FixedArray> fixed =
isolate_->factory()->NewFixedArray(2 * table_size);
instance->function_table = fixed;
DCHECK_EQ(0u, module->function_table.size());
for (int i = 0; i < table_size; i++) {
module_.function_table.push_back(functions[i]);
void AddIndirectFunctionTable(uint16_t* functions, uint32_t table_size) {
module_.function_tables.push_back(
{table_size, table_size, std::vector<uint16_t>()});
for (uint32_t i = 0; i < table_size; ++i) {
module_.function_tables.back().values.push_back(functions[i]);
}
Handle<FixedArray> values = BuildFunctionTable(
isolate_, static_cast<int>(module_.function_tables.size() - 1),
&module_);
instance->function_tables.push_back(values);
}
void PopulateIndirectFunctionTable() {
if (instance->function_table.is_null()) return;
int table_size = static_cast<int>(module->function_table.size());
for (int i = 0; i < table_size; i++) {
int function_index = module->function_table[i];
const WasmFunction* function = &module->functions[function_index];
instance->function_table->set(i, Smi::FromInt(function->sig_index));
instance->function_table->set(i + table_size,
*instance->function_code[function_index]);
for (uint32_t i = 0; i < instance->function_tables.size(); i++) {
PopulateFunctionTable(instance->function_tables[i],
module_.function_tables[i].size,
&instance->function_code);
}
}
......
......@@ -64,7 +64,6 @@ var kDeclExports = 0x09;
var kDeclFunctions = 0x0a;
var kDeclCode = 0x0b;
var kDeclNames = 0x0c;
var kDeclFunctionTablePad = 0x0d;
var kArity0 = 0;
var kArity1 = 1;
......@@ -75,7 +74,7 @@ var kWasmFunctionTypeForm = 0x40;
var section_names = [
"memory", "type", "old_function", "global", "data",
"table", "end", "start", "import", "export",
"function", "code", "name", "table_pad"];
"function", "code", "name"];
// Function declaration flags
var kDeclFunctionName = 0x01;
......
......@@ -227,6 +227,10 @@ class WasmModuleBuilder {
if (debug) print("emitting table @ " + binary.length);
binary.emit_section(kDeclTable, section => {
section.emit_varint(wasm.table.length);
if (wasm.pad !== null) {
if (debug) print("emitting table padding @ " + binary.length);
section.emit_varint(wasm.pad);
}
for (let index of wasm.table) {
section.emit_varint(index);
}
......@@ -338,19 +342,6 @@ class WasmModuleBuilder {
});
}
// Add an indirect function table pad section.
if (wasm.pad !== null) {
if (debug)
print("emitting indirect function table pad @ " + binary.length);
binary.emit_section(kDeclFunctionTablePad, section => {
section.emit_varint(wasm.pad);
});
}
// End the module.
if (debug) print("emitting end @ " + binary.length);
binary.emit_section(kDeclEnd, section => {});
return binary;
}
......
......@@ -507,8 +507,9 @@ TEST_F(WasmModuleVerifyTest, OneIndirectFunction) {
if (result.ok()) {
EXPECT_EQ(1, result.val->signatures.size());
EXPECT_EQ(1, result.val->functions.size());
EXPECT_EQ(1, result.val->function_table.size());
EXPECT_EQ(0, result.val->function_table[0]);
EXPECT_EQ(1, result.val->function_tables.size());
EXPECT_EQ(1, result.val->function_tables[0].values.size());
EXPECT_EQ(0, result.val->function_tables[0].values[0]);
}
if (result.val) delete result.val;
}
......@@ -539,9 +540,10 @@ TEST_F(WasmModuleVerifyTest, MultipleIndirectFunctions) {
if (result.ok()) {
EXPECT_EQ(2, result.val->signatures.size());
EXPECT_EQ(4, result.val->functions.size());
EXPECT_EQ(8, result.val->function_table.size());
EXPECT_EQ(1, result.val->function_tables.size());
EXPECT_EQ(8, result.val->function_tables[0].values.size());
for (int i = 0; i < 8; i++) {
EXPECT_EQ(i & 3, result.val->function_table[i]);
EXPECT_EQ(i & 3, result.val->function_tables[0].values[i]);
}
}
if (result.val) delete result.val;
......
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