Skip to content

Commit

Permalink
Generate actual bytecode using fuel_asm (#52)
Browse files Browse the repository at this point in the history
* begin register allocator

* begin reg alloc

* mutable virtual registers; basic allocation algorithm skeleton

* mutable registers in allocation

* pull in fuel-asm official ops

* switching laptops

* begin work on virtual registers and ops

* daily checkpoint

* add AllocatedOp abstraction

* template for parsing ops

* allocation algorithm progress

* change op parsing logic

* WIP parsing inline asm to new ops

* more op parsing

* finish parsing virtual ops from asm

* start registers method

* register allocation method

* convert virtual registers to allocated ones

* switch back to organizational labels for jumps

* realized ops

* fully allocate registers and resolve labels

* print allocated registers

* fill in todo!() errors in asm parsing

* resolve all todosudo apt-get install vlc in core_lang

* switch to ssh for fuel-asm

* resolve warnings

* fix git url

* rustfmt

* small self-code-review

* resolve module

* map the virtual opcodes to fuel_asm ops

* code review feedback

* factor finalized asm out into its own file

* realize data section and instructions to bits

* data section offset label

* initial bytecode generation

* add forc --asm command

* print out the loading of the data section op

* resolve warnings

* fix register allocater bug

* cleanup

* fix bad error message

* code review feedback

* fix doctest

* fix typo

* reference fuel_core for register constants

* new ssh key

* git change rust version

* wrong rust version

Co-authored-by: Alexander Hansen <alexanderhansen@Alexanders-MacBook-Pro.local>
  • Loading branch information
sezna and Alexander Hansen authored May 30, 2021
1 parent 7683b7f commit fb192d9
Show file tree
Hide file tree
Showing 20 changed files with 799 additions and 187 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/cargo_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ jobs:
uses: webfactory/ssh-agent@v0.5.2
with:
ssh-private-key: |
${{ secrets.PRIVATE_KEY_FUEL_CORE }}
${{ secrets.PRIVATE_KEY_FUEL_ASM }}
${{ secrets.PRIVATE_KEY_FUEL_TX }}
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.50.0
toolchain: 1.52.0
override: true

- name: Install rustfmt
Expand Down
1 change: 1 addition & 0 deletions core_lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ Inflector = "0.11"
petgraph = "0.5"
uuid-b64 = "0.1"
fuel-asm = { git = "ssh://git@github.com/FuelLabs/fuel-asm.git" }
fuel-vm-rust = { git = "ssh://git@github.com/FuelLabs/fuel-core.git" }
2 changes: 1 addition & 1 deletion core_lang/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ It is recommended to run this compiler from the `forc` executable, which can be

## Minimum supported Rust version

As of now, this code was developed on and is guaranteed to run on Rust 1.50 stable.
As of now, this code was developed on and is guaranteed to run on Rust 1.52 stable.
13 changes: 12 additions & 1 deletion core_lang/src/asm_generation/compiler_constants.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
pub(crate) const NUM_FREE_REGISTERS: u8 = 48;
/// The number of registers available for the compiler to use. Registers reserved by the
/// compiler are contained within these.
const NUM_FREE_REGISTERS: u8 = 48;
pub(crate) const TWENTY_FOUR_BITS: u64 = 0b111_111_111_111_111_111_111_111;
pub(crate) const EIGHTEEN_BITS: u64 = 0b111_111_111_111_111_111;
pub(crate) const TWELVE_BITS: u64 = 0b111_111_111_111;

/// This is the number of registers reserved by the compiler. Adjust this number if a new
/// reservation must be made.
/// So far, the compiler-reserved registers are:
/// 1. DATA_SECTION_BEGIN
const NUM_COMPILER_RESERVED_REGISTERS: u8 = 1;
pub(crate) const DATA_SECTION_REGISTER: u8 = NUM_FREE_REGISTERS - 2;
pub(crate) const NUM_ALLOCATABLE_REGISTERS: u8 =
NUM_FREE_REGISTERS - NUM_COMPILER_RESERVED_REGISTERS;
9 changes: 6 additions & 3 deletions core_lang/src/asm_generation/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,15 @@ fn convert_literal_to_asm<'sc>(
let data_id = namespace.insert_data_value(lit);
// then get that literal id and use it to make a load word op
vec![Op {
opcode: either::Either::Right(OrganizationalOp::Ld(return_register.clone(), data_id)),
opcode: either::Either::Left(VirtualOp::LW(return_register.clone(), data_id)),
comment: "literal instantiation".into(),
owning_span: Some(span),
}]
}

/// For now, all functions are handled by inlining at the time of application.
fn convert_fn_app_to_asm<'sc>(
_name: &CallPath<'sc>,
name: &CallPath<'sc>,
arguments: &[(Ident<'sc>, TypedExpression<'sc>)],
function_body: &TypedCodeBlock<'sc>,
parent_namespace: &mut AsmNamespace<'sc>,
Expand All @@ -342,7 +342,10 @@ fn convert_fn_app_to_asm<'sc>(
) -> CompileResult<'sc, Vec<Op<'sc>>> {
let mut warnings = vec![];
let mut errors = vec![];
let mut asm_buf = vec![];
let mut asm_buf = vec![Op::new_comment(format!(
"{} fn call",
name.suffix.primary_name
))];
// Make a local namespace so that the namespace of this function does not pollute the outer
// scope
let mut namespace = parent_namespace.clone();
Expand Down
79 changes: 79 additions & 0 deletions core_lang/src/asm_generation/finalized_asm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use super::{DataSection, InstructionSet};
use crate::error::*;
use either::Either;
use std::io::Write;
/// Represents an ASM set which has had register allocation, jump elimination, and optimization
/// applied to it
pub enum FinalizedAsm<'sc> {
ContractAbi,
ScriptMain {
data_section: DataSection<'sc>,
program_section: InstructionSet<'sc>,
},
PredicateMain {
data_section: DataSection<'sc>,
program_section: InstructionSet<'sc>,
},
// Libraries do not generate any asm.
Library,
}

impl<'sc> FinalizedAsm<'sc> {
pub(crate) fn to_bytecode(&self) -> CompileResult<'sc, Vec<u8>> {
use FinalizedAsm::*;
match self {
ContractAbi | Library => ok(vec![], vec![], vec![]),
ScriptMain {
program_section,
data_section,
} => to_bytecode(program_section, data_section),
PredicateMain {
program_section,
data_section,
} => to_bytecode(program_section, data_section),
}
}
}

fn to_bytecode<'sc>(
program_section: &InstructionSet<'sc>,
data_section: &DataSection<'sc>,
) -> CompileResult<'sc, Vec<u8>> {
let mut errors = vec![];
if program_section.ops.len() & 1 != 0 {
println!("ops len: {}", program_section.ops.len());
errors.push(CompileError::Internal(
"Non-word-aligned (odd-number) ops generated. This is an invariant violation.",
pest::Span::new(" ", 0, 0).unwrap(),
));
return err(vec![], errors);
}
// The below invariant is introduced to word-align the data section.
// A noop is inserted in ASM generation if there is an odd number of ops.
assert_eq!(program_section.ops.len() & 1, 0);
let offset_to_data_section = (program_section.ops.len() * 4) as u64;

// each op is four bytes, so the length of the buf is then number of ops times four.
let mut buf = vec![0; program_section.ops.len() * 4];

for (ix, op) in program_section.ops.iter().enumerate() {
let op = op.to_fuel_asm(offset_to_data_section, data_section);
match op {
Either::Right(data) => {
for i in 0..data.len() {
buf[ix + i] = data[i];
}
}
Either::Left(mut op) => {
op.write(&buf[ix * 4..])
.expect("Failed to write to in-memory buffer.");
}
}
}

let mut data_section = data_section.serialize_to_bytes();

buf.append(&mut data_section);

ok(buf, vec![], errors)
}
Loading

0 comments on commit fb192d9

Please sign in to comment.