Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
wanderer committed Dec 21, 2016
0 parents commit e054637
Show file tree
Hide file tree
Showing 59 changed files with 3,259 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "test/wabt"]
path = test/wabt
url = git@github.com:WebAssembly/wabt.git
16 changes: 16 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
language: node_js
node_js:
- "7"
env:
matrix:
- TEST_SUITE=test
matrix:
fast_finish: true
include:
- os: linux
node_js: "7"
env: TEST_SUITE=coveralls
- os: linux
node_js: "7"
env: TEST_SUITE=lint
script: npm run $TEST_SUITE
Empty file added README.md
Empty file.
21 changes: 21 additions & 0 deletions defaultCostTable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"start": 1,
"type": {
"params": {
"DEFAULT": 1
},
"return_type": {
"DEFAULT": 1
}
},
"import": 1,
"code": {
"locals": {
"DEFAULT": 1
},
"code": {
"DEFAULT": 1
}
},
"data": 1
}
196 changes: 196 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
const text2json = require('wasm-json-toolkit').text2json
const SECTION_IDS = require('wasm-json-toolkit/json2wasm').SECTION_IDS

function getCost (json, costTable = {}, defaultCost = 0) {
let cost = 0
defaultCost = costTable['DEFAULT'] !== undefined ? costTable['DEFAULT'] : 0

if (typeof costTable === 'function') {
cost = costTable(json)
} else if (Array.isArray(json)) {
json.forEach(el => {
cost += getCost(el, costTable)
})
} else if (typeof costTable === 'number') {
cost = costTable
} else if (typeof json === 'object') {
for (const propName in json) {
const propCost = costTable[propName]
if (propCost) {
cost += getCost(json[propName], propCost, defaultCost)
}
}
} else if (costTable[json] === undefined) {
cost = defaultCost
} else {
cost = costTable[json]
}
return cost
}

exports.meter = function meter (json, costTable) {
function findSection (module, sectionName) {
return module.find(section => section.name === sectionName)
}

function createSection (module, sectionName) {
const newSectionId = SECTION_IDS[sectionName]
for (let index in module) {
const section = module[index]
const sectionId = SECTION_IDS[section.name]
if (sectionId) {
if (newSectionId < sectionId) {
// inject a new section
module.splice(index, 0, {
name: sectionName,
entries: []
})
return
}
}
}
}

const importJson = {
'moduleStr': 'metering',
'fieldStr': 'usegas',
'kind': 'function'
}
const importType = {
'form': 'func',
'params': ['i64']
}

let funcIndex = 0
let initialCost = 0
let functionModule, typeModule

json = json.slice(0)

// add nessicarry sections iff they don't exist
if (!findSection(json, 'type')) createSection(json, 'type')
if (!findSection(json, 'import')) createSection(json, 'import')

for (let section of json) {
section = Object.assign(section)
switch (section.name) {
case 'type':
// mark the import index
importJson.type = section.entries.push(importType) - 1
// save for use for the code section
typeModule = section
break
case 'function':
// save for use for the code section
functionModule = section
break
case 'import':
for (const entry of section.entries) {
initialCost += getCost(entry, costTable.import)
if (entry.kind === 'function') {
funcIndex++
}
}
// append the metering import
section.entries.push(importJson)
break
case 'export':
for (const entry of section.entries) {
initialCost += getCost(entry, costTable.global)
if (entry.kind === 'function' && entry.index >= funcIndex) {
entry.index++
}
}
break
case 'element':
for (const entry of section.entries) {
initialCost += getCost(entry, costTable.element)
// remap elements indices
entry.elements = entry.elements.map(el => el >= funcIndex ? ++el : el)
}
break
case 'start':
initialCost += getCost(section, costTable.start)
// remap start index
if (section.index >= funcIndex) section.index++
break
case 'code':
for (const i in section.entries) {
const entry = section.entries[i]
const typeIndex = functionModule.entries[i]
const type = typeModule.entries[typeIndex]
const cost = getCost(type, costTable.type)

exports.meterCodeEntry(entry, costTable.code, funcIndex, cost)
}
break
default:
if (section.entries) {
for (const entry of section.entries) {
initialCost += getCost(entry, costTable[section.name])
}
}
}
}
return {
initialCost: initialCost,
module: json
}
}

exports.meterCodeEntry = (entry, costTable, meterFuncIndex, cost = 0) => {
function meteringStatement (cost, meteringImportIndex) {
return text2json(`i64.const ${cost} call ${meteringImportIndex}`)
}
function remapOp (op, funcIndex) {
if (op.name === 'call' && op.immediates >= funcIndex) {
op.immediates = (++op.immediates).toString()
}
}
function meterTheMeteringStatement () {
const code = meteringStatement(0, 0)
// sum the operations cost
return code.reduce(
(sum, op) => sum + getCost(op.name, costTable.code)
, 0)
}

const branchingOps = new Set(['end', 'br', 'br_table', 'br_if', 'if', 'else', 'return', 'loop'])
const meteringOverHead = meterTheMeteringStatement()
let code = entry.code.slice()
let meteredCode = []

cost += getCost(entry.locals, costTable.local)

while (code.length) {
let i = 0

// meters a segment of wasm code
while (true) {
const op = code[i++]
remapOp(op, meterFuncIndex)

cost += getCost(op.name, costTable.code)
if (branchingOps.has(op.name)) {
break
}
}

// add the metering statement
if (cost !== 0) {
// add the cost of metering
cost += meteringOverHead
meteredCode = meteredCode
.concat(meteringStatement(cost, meterFuncIndex))
}

// start a new segment
meteredCode = meteredCode
.concat(code.slice(0, i))
code = code.slice(i)
cost = 0
}

entry.code = meteredCode
return entry
}
42 changes: 42 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "wasm-metering",
"version": "0.1.0",
"description": "injects metering into a webassembly binary",
"main": "index.js",
"scripts": {
"coverage": "node --harmony ./node_modules/istanbul/lib/cli.js cover ./test/index.js",
"coveralls": "npm run coverage && coveralls <coverage/lcov.info",
"lint": "standard",
"test": "node --harmony ./test/index.js"
},
"author": "mjbecze <mjbecze@gmail.com>",
"license": "MPL-2.0",
"dependencies": {
"leb128": "0.0.0",
"wasm-json-toolkit": "0.0.2"
},
"devDependencies": {
"coveralls": "^2.11.4",
"istanbul": "^0.4.1",
"standard": "^8.6.0",
"tape": "^4.6.3"
},
"keywords": [
"wasm",
"metering",
"webassembly"
],
"repository": {
"type": "git",
"url": "https://github.com/ewasm/wasm-metering.git"
},
"bugs": {
"url": "https://github.com/ewasm/wasm-metering/issues"
},
"homepage": "https://github.com/ewasm/wasm-metering",
"standard": {
"ignore": [
"test/wabt"
]
}
}
25 changes: 25 additions & 0 deletions test/buildTests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const fs = require('fs')
const cp = require('child_process')
const path = require('path')
const wasm2json = require('wasm-json-toolkit/wasm2json')

function processFiles (path, out) {
const binPath = `${__dirname}/wabt/out/wast2wasm`
const files = fs.readdirSync(path)

for (let file of files) {
console.log(file)
if (file.split('.')[1] === 'wast') {
// compile to wasm
const str = `${binPath} ${path}/${file} -o /tmp/temp.wasm`
cp.execSync(str)
const wasm = fs.readFileSync('/tmp/temp.wasm')
// compile to json
const json = wasm2json(wasm)
fs.writeFileSync(`${out}/${file}.json`, JSON.stringify(json, null, 2))
}
}
}

processFiles(path.join(__dirname, './in/wast'), path.join(__dirname, './in/json'))
processFiles(path.join(__dirname, './expected-out/wast'), path.join(__dirname, './expected-out/json'))
28 changes: 28 additions & 0 deletions test/costTables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const tape = require('tape')
const metering = require('../').meter
const json = require('./memory.json')

tape('different cost tables', t => {
let costTable = require('./generativeCostTable.js')
let meteredJson = metering(json, costTable)
// let expectedJson = require(`${__dirname}/
t.deepEquals(meteredJson.initialCost, 20, 'should generat costs for memory')

costTable.data = 5
meteredJson = metering(json, costTable)

t.deepEquals(meteredJson.initialCost, 30, 'should use interger for section type')

costTable.data = {
'offset': {
'return_type': {
'i32': 3
}
}
}

meteredJson = metering(json, costTable)
t.deepEquals(meteredJson.initialCost, 26, 'should use nested keys for metering')
t.end()
})

13 changes: 13 additions & 0 deletions test/expected-out/initCosts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"basic+import.wast.json": 1,
"basic.wast.json": 0,
"element.wast.json": 2,
"fac.wast.json": 0,
"memory0.wast.json": 20,
"memory1.wast.json": 30,
"memory2.wast.json": 26,
"mixedImports.wast.json": 2,
"start.wast.json": 4,
"zeroCostOps.wast.json": 0,
"stuff.wast.json": 3
}
Loading

0 comments on commit e054637

Please sign in to comment.