Added position independent code option for asm.

This commit is contained in:
Richard Moore 2020-02-06 03:30:31 -05:00
parent a33bf0e37f
commit 89615c59d3
No known key found for this signature in database
GPG Key ID: 665176BE8E9DC651
2 changed files with 70 additions and 13 deletions

@ -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) {