Added position independent code option for asm.
This commit is contained in:
parent
a33bf0e37f
commit
89615c59d3
@ -1,10 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// @TODO:
|
// @TODO:
|
||||||
// - PIC
|
|
||||||
// - warn on opcode non-function iff parameters
|
|
||||||
// - warn return/revert non-empty, comment ; !assert(+1 @extra)
|
// - warn return/revert non-empty, comment ; !assert(+1 @extra)
|
||||||
// - $$
|
|
||||||
// - In JS add config (positionIndependent)
|
// - In JS add config (positionIndependent)
|
||||||
// - When checking name collisions, verify no collision in javascript
|
// - When checking name collisions, verify no collision in javascript
|
||||||
|
|
||||||
@ -267,16 +264,19 @@ export class LinkNode extends ValueNode {
|
|||||||
|
|
||||||
let value: number = null;
|
let value: number = null;
|
||||||
|
|
||||||
|
let isOffset = false;
|
||||||
|
|
||||||
const target = assembler.getTarget(this.label);
|
const target = assembler.getTarget(this.label);
|
||||||
if (target instanceof LabelNode) {
|
if (target instanceof LabelNode) {
|
||||||
if (this.type === "offset") {
|
if (this.type === "offset") {
|
||||||
//value = assembler.getOffset(this.label);
|
|
||||||
value = (<number>(assembler.getLinkValue(target, this)));
|
value = (<number>(assembler.getLinkValue(target, this)));
|
||||||
|
isOffset = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const result = (<DataSource>(assembler.getLinkValue(target, this)));
|
const result = (<DataSource>(assembler.getLinkValue(target, this)));
|
||||||
if (this.type === "offset") {
|
if (this.type === "offset") {
|
||||||
value = result.offset;
|
value = result.offset;
|
||||||
|
isOffset = true;
|
||||||
} else if (this.type === "length") {
|
} else if (this.type === "length") {
|
||||||
value = result.length;
|
value = result.length;
|
||||||
}
|
}
|
||||||
@ -286,7 +286,42 @@ export class LinkNode extends ValueNode {
|
|||||||
throw new Error("labels can only be targetted as offsets");
|
throw new Error("labels can only be targetted as offsets");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOffset && assembler.positionIndependentCode) {
|
||||||
|
const here = assembler.getOffset(this, this);
|
||||||
|
|
||||||
|
const opcodes = [ ];
|
||||||
|
|
||||||
|
// Have to jump backwards
|
||||||
|
if (here > value) {
|
||||||
|
// Find a literal which can include its own length in the delta
|
||||||
|
let literal = "0x";
|
||||||
|
for (let w = 1; w <= 5; w++) {
|
||||||
|
if (w > 4) { throw new Error("jump too large!"); }
|
||||||
|
literal = pushLiteral(here - value + w);
|
||||||
|
if (ethers.utils.hexDataLength(literal) <= w) {
|
||||||
|
literal = ethers.utils.hexZeroPad(literal, w);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opcodes.push(literal);
|
||||||
|
opcodes.push(Opcode.from("PC"));
|
||||||
|
opcodes.push(Opcode.from("SUB"));
|
||||||
|
|
||||||
|
// This also works, in case the above literal thing doesn't work out...
|
||||||
|
//opcodes.push(Opcode.from("PC"));
|
||||||
|
//opcodes.push(pushLiteral(-delta));
|
||||||
|
//opcodes.push(Opcode.from("SWAP1"));
|
||||||
|
//opcodes.push(Opcode.from("SUB"));
|
||||||
|
} else {
|
||||||
|
opcodes.push(Opcode.from("PC"));
|
||||||
|
opcodes.push(pushLiteral(value - here));
|
||||||
|
opcodes.push(Opcode.from("ADD"));
|
||||||
|
}
|
||||||
|
visit(this, hexConcat(opcodes));
|
||||||
|
} else {
|
||||||
visit(this, pushLiteral(value));
|
visit(this, pushLiteral(value));
|
||||||
|
}
|
||||||
|
|
||||||
assembler.end(this);
|
assembler.end(this);
|
||||||
}
|
}
|
||||||
@ -609,14 +644,16 @@ export type ParserOptions = {
|
|||||||
|
|
||||||
class Assembler {
|
class Assembler {
|
||||||
readonly root: Node;
|
readonly root: Node;
|
||||||
|
readonly positionIndependentCode: boolean;
|
||||||
|
|
||||||
readonly nodes: { [ tag: string ]: NodeState };
|
readonly nodes: { [ tag: string ]: NodeState };
|
||||||
readonly labels: { [ name: string ]: LabelledNode };
|
readonly labels: { [ name: string ]: LabelledNode };
|
||||||
|
|
||||||
_parents: { [ tag: string ]: Node };
|
_parents: { [ tag: string ]: Node };
|
||||||
|
|
||||||
constructor(root: Node) {
|
constructor(root: Node, positionIndependentCode?: boolean) {
|
||||||
ethers.utils.defineReadOnly(this, "root", root);
|
ethers.utils.defineReadOnly(this, "root", root);
|
||||||
|
ethers.utils.defineReadOnly(this, "positionIndependentCode", !!positionIndependentCode);
|
||||||
|
|
||||||
const nodes: { [ tag: string ]: NodeState } = { };
|
const nodes: { [ tag: string ]: NodeState } = { };
|
||||||
const labels: { [ name: string ]: LabelledNode } = { };
|
const labels: { [ name: string ]: LabelledNode } = { };
|
||||||
@ -689,6 +726,19 @@ class Assembler {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOffset(node: Node, source?: Node): number {
|
||||||
|
const offset = this.nodes[node.tag].offset;
|
||||||
|
|
||||||
|
if (source == null) { return offset }
|
||||||
|
|
||||||
|
const sourceScope: ScopeNode = ((source instanceof ScopeNode) ? source: this.getAncestor<ScopeNode>(source, ScopeNode));
|
||||||
|
return offset - this.nodes[sourceScope.tag].offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOffset(node: Node, offset: number): void {
|
||||||
|
this.nodes[node.tag].offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
getBytecode(node: Node): string {
|
getBytecode(node: Node): string {
|
||||||
return this.nodes[node.tag].bytecode;
|
return this.nodes[node.tag].bytecode;
|
||||||
}
|
}
|
||||||
@ -870,7 +920,6 @@ class SemanticChecker extends Assembler {
|
|||||||
|
|
||||||
class CodeGenerationAssembler extends Assembler {
|
class CodeGenerationAssembler extends Assembler {
|
||||||
readonly filename: string;
|
readonly filename: string;
|
||||||
readonly positionIndependentCode: boolean;
|
|
||||||
readonly retry: number;
|
readonly retry: number;
|
||||||
|
|
||||||
readonly defines: { [ name: string ]: any };
|
readonly defines: { [ name: string ]: any };
|
||||||
@ -885,9 +934,8 @@ class CodeGenerationAssembler extends Assembler {
|
|||||||
_changed: boolean;
|
_changed: boolean;
|
||||||
|
|
||||||
constructor(root: Node, options: AssemblerOptions) {
|
constructor(root: Node, options: AssemblerOptions) {
|
||||||
super(root);
|
super(root, !!options.positionIndependentCode);
|
||||||
|
|
||||||
ethers.utils.defineReadOnly(this, "positionIndependentCode", !!options.positionIndependentCode);
|
|
||||||
ethers.utils.defineReadOnly(this, "retry", ((options.retry != null) ? options.retry: 512));
|
ethers.utils.defineReadOnly(this, "retry", ((options.retry != null) ? options.retry: 512));
|
||||||
ethers.utils.defineReadOnly(this, "filename", resolve(options.filename || "./contract.asm"));
|
ethers.utils.defineReadOnly(this, "filename", resolve(options.filename || "./contract.asm"));
|
||||||
ethers.utils.defineReadOnly(this, "defines", Object.freeze(options.defines || { }));
|
ethers.utils.defineReadOnly(this, "defines", Object.freeze(options.defines || { }));
|
||||||
@ -986,16 +1034,18 @@ class CodeGenerationAssembler extends Assembler {
|
|||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
|
||||||
await this.root.assemble(this, (node, bytecode) => {
|
await this.root.assemble(this, (node, bytecode) => {
|
||||||
const state = this.nodes[node.tag];
|
|
||||||
|
|
||||||
// Things have moved; we will need to try again
|
// Things have moved; we will need to try again
|
||||||
if (state.offset !== offset) {
|
if (this.getOffset(node) !== offset) {
|
||||||
state.offset = offset;
|
this.setOffset(node, offset);
|
||||||
this._didChange();
|
this._didChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
this._stack.forEach((node) => {
|
this._stack.forEach((node) => {
|
||||||
this.setBytecode(node, hexConcat([ this.getBytecode(node), bytecode ]));
|
this.setBytecode(node, hexConcat([
|
||||||
|
this.getBytecode(node),
|
||||||
|
bytecode
|
||||||
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
offset += ethers.utils.hexDataLength(bytecode);
|
offset += ethers.utils.hexDataLength(bytecode);
|
||||||
|
@ -32,6 +32,7 @@ class AssemblePlugin extends Plugin {
|
|||||||
|
|
||||||
disassemble: boolean;
|
disassemble: boolean;
|
||||||
ignoreWarnings: boolean;
|
ignoreWarnings: boolean;
|
||||||
|
pic: boolean;
|
||||||
target: string;
|
target: string;
|
||||||
|
|
||||||
defines: { [ key: string ]: string | boolean }
|
defines: { [ key: string ]: string | boolean }
|
||||||
@ -56,6 +57,10 @@ class AssemblePlugin extends Plugin {
|
|||||||
name: "--ignore-warnings",
|
name: "--ignore-warnings",
|
||||||
help: "Ignore warnings"
|
help: "Ignore warnings"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "--pic",
|
||||||
|
help: "generate position independent code"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "--target LABEL",
|
name: "--target LABEL",
|
||||||
help: "output LABEL bytecode (default: _)"
|
help: "output LABEL bytecode (default: _)"
|
||||||
@ -80,6 +85,7 @@ class AssemblePlugin extends Plugin {
|
|||||||
this.disassemble = argParser.consumeFlag("disassemble");
|
this.disassemble = argParser.consumeFlag("disassemble");
|
||||||
|
|
||||||
this.ignoreWarnings = argParser.consumeFlag("ignore-warnings");
|
this.ignoreWarnings = argParser.consumeFlag("ignore-warnings");
|
||||||
|
this.pic = argParser.consumeFlag("pic");
|
||||||
this.target = argParser.consumeOption("target");
|
this.target = argParser.consumeOption("target");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +165,7 @@ class AssemblePlugin extends Plugin {
|
|||||||
console.log(await assemble(ast, {
|
console.log(await assemble(ast, {
|
||||||
defines: this.defines,
|
defines: this.defines,
|
||||||
filename: this.filename,
|
filename: this.filename,
|
||||||
|
positionIndependentCode: this.pic,
|
||||||
target: (this.target || "_")
|
target: (this.target || "_")
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user