mirror of
https://github.com/kmc7468/cs420.git
synced 2025-12-14 22:38:46 +00:00
Update homework 1 and 2
This commit is contained in:
34
README.md
34
README.md
@@ -34,7 +34,7 @@ cargo test --release # release build test
|
||||
cargo test <test-name> # run a particular test
|
||||
```
|
||||
|
||||
`<test-name>` can be `test_ast_print`, `ir_smoke`, ...
|
||||
`<test-name>` can be `test_examples_write_c`, `test_examples_irgen`, ...
|
||||
|
||||
|
||||
## Fuzzing
|
||||
@@ -44,10 +44,7 @@ cargo test <test-name> # run a particular test
|
||||
```sh
|
||||
# Ubuntu 18.04 or higher
|
||||
apt install -y make cmake python3
|
||||
|
||||
# MacOS
|
||||
xcode-select install
|
||||
brew install cmake python3
|
||||
apt install -y csmith libcsmith-dev creduce
|
||||
```
|
||||
|
||||
### Run
|
||||
@@ -59,5 +56,28 @@ python3 tests/fuzz.py --help # print options
|
||||
python3 tests/fuzz.py --print -n10 # test C AST printer for 10 times
|
||||
```
|
||||
|
||||
We use [Csmith](https://embed.cs.utah.edu/csmith/) to randomly generate C source codes. Csmith will
|
||||
be automatically downloaded and built by the test script.
|
||||
We use `csmith` to randomly generate C source codes. `csmith` will be automatically downloaded and
|
||||
built by the test script. For more information, we refer to the
|
||||
[Csmith](https://embed.cs.utah.edu/csmith/) homepage.
|
||||
|
||||
### Reduce
|
||||
|
||||
When the fuzzer finds a buggy input program for your compiler, it is highly likely that the input
|
||||
program is too big to manually inspect. We use `creduce` that reduces the buggy input program as
|
||||
much as possible.
|
||||
|
||||
Suppose `tests/test_polished.c` is the buggy input program. Then the following script reduces the
|
||||
program to `tests/test_reduced.c`:
|
||||
|
||||
```sh
|
||||
python3 tests/fuzz.py --reduce <fuzz-option>
|
||||
```
|
||||
|
||||
`<fuzz-option>` can be `--print` or `--irgen`. It shall be the one used in [Run](#run).
|
||||
|
||||
### How it reduces test case?
|
||||
|
||||
The script performs unguided test-case reduction using `creduce`: given a buggy program, it randomly
|
||||
reduces the program; check if the reduced program still fails on the test, and if so, replaces the
|
||||
given program with the reduced one; repeat until you get a small enough buggy program. For more
|
||||
information, we refer to the [Creduce](https://embed.cs.utah.edu/creduce/) homepage.
|
||||
|
||||
@@ -6,6 +6,7 @@ use clap::{crate_authors, crate_description, crate_version, App};
|
||||
extern crate kecc;
|
||||
|
||||
use kecc::{Parse, Translate};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let yaml = load_yaml!("fuzz_cli.yml");
|
||||
@@ -20,7 +21,10 @@ fn main() {
|
||||
let unit = ok_or_exit!(Parse::default().translate(&input), 1);
|
||||
|
||||
if matches.is_present("print") {
|
||||
kecc::write_c_test(&unit);
|
||||
return;
|
||||
kecc::test_write_c(&unit, Path::new(&input));
|
||||
}
|
||||
|
||||
if matches.is_present("irgen") {
|
||||
kecc::test_irgen(&unit, Path::new(&input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ args:
|
||||
short: p
|
||||
long: print
|
||||
help: Fuzzes C AST printer
|
||||
- irgen:
|
||||
short: i
|
||||
long: irgen
|
||||
help: Fuzzes irgen
|
||||
- INPUT:
|
||||
help: Sets the input file to use
|
||||
required: true
|
||||
|
||||
10
bin/kecc.rs
10
bin/kecc.rs
@@ -5,7 +5,7 @@ use clap::{crate_authors, crate_description, crate_version, App};
|
||||
#[macro_use]
|
||||
extern crate kecc;
|
||||
|
||||
use kecc::{Codegen, Irgen, Optimize, Parse, Translate, O1};
|
||||
use kecc::{write, Asmgen, Irgen, Optimize, Parse, Translate, O1};
|
||||
|
||||
fn main() {
|
||||
let yaml = load_yaml!("kecc_cli.yml");
|
||||
@@ -27,7 +27,7 @@ fn main() {
|
||||
};
|
||||
|
||||
if matches.is_present("print") {
|
||||
kecc::write_c(&unit, &mut output).unwrap();
|
||||
write(&unit, &mut output).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ fn main() {
|
||||
}
|
||||
};
|
||||
if matches.is_present("irgen") {
|
||||
kecc::write_ir(&ir, &mut output).unwrap();
|
||||
write(&ir, &mut output).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,6 @@ fn main() {
|
||||
O1::default().optimize(&mut ir);
|
||||
}
|
||||
|
||||
let asm = ok_or_exit!(Codegen::default().translate(&ir), 1);
|
||||
kecc::write_asm(&asm, &mut output);
|
||||
let asm = ok_or_exit!(Asmgen::default().translate(&ir), 1);
|
||||
write(&asm, &mut output).unwrap();
|
||||
}
|
||||
|
||||
1
examples/.gitignore
vendored
Normal file
1
examples/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.exe
|
||||
3205
examples/hw1/complete_cond.c
Normal file
3205
examples/hw1/complete_cond.c
Normal file
File diff suppressed because it is too large
Load Diff
13
examples/hw1/cond_and_loop.c
Normal file
13
examples/hw1/cond_and_loop.c
Normal file
@@ -0,0 +1,13 @@
|
||||
int main() {
|
||||
int i;
|
||||
int p = 2;
|
||||
int q = 5;
|
||||
int r = (0 ? ((p > q) ? (p -= 2) : (p += 2)) : (p + q));
|
||||
|
||||
for (i = 0; i < 11; ((i % 2) ? (i += 2) : ++i)) {
|
||||
if (i % 2) { p += q; }
|
||||
else { p += r; }
|
||||
}
|
||||
|
||||
return p == 34;
|
||||
}
|
||||
15
examples/hw1/gcd.c
Normal file
15
examples/hw1/gcd.c
Normal file
@@ -0,0 +1,15 @@
|
||||
int gcd(int a, int b) {
|
||||
a = (a > 0) ? a : -a;
|
||||
b = (b > 0) ? b : -b;
|
||||
|
||||
while(a != b) {
|
||||
if(a > b) { a -= b; }
|
||||
else { b -= a; }
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
int main() {
|
||||
return gcd(18, 21) == 3;
|
||||
}
|
||||
53
examples/meta/make_cond.py
Normal file
53
examples/meta/make_cond.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Make c program which uses complicated conditional expression
|
||||
|
||||
To make c program, execute `python3 make_cond.py > file_name.c`
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
def eval_cond(arr_cond):
|
||||
"""Evaluate conditional expression
|
||||
"""
|
||||
if len(arr_cond) == 1:
|
||||
return arr_cond[0]
|
||||
new_arr_cond = []
|
||||
for cond_start in range(len(arr_cond) // 3):
|
||||
cond_val = arr_cond[3*cond_start + 1] if arr_cond[3*cond_start] else arr_cond[3*cond_start + 2]
|
||||
new_arr_cond.append(cond_val)
|
||||
return eval_cond(new_arr_cond)
|
||||
|
||||
def make_func(i):
|
||||
"""Make a function that contains a conditional expression
|
||||
"""
|
||||
func_signature = "int " + "func_" + str(i) + "()"
|
||||
variables = "abcdefghijklmnopqrstuvwxyzA"
|
||||
func_inner = []
|
||||
val_bitmap = []
|
||||
|
||||
# Variable initializiation
|
||||
for var in variables:
|
||||
val = random.randint(0, 1)
|
||||
val_bitmap.append(val)
|
||||
decl = "\tint " + var + " = " + str(val) + ";"
|
||||
func_inner.append(decl)
|
||||
|
||||
expr_val = eval_cond(val_bitmap)
|
||||
func_inner.append("\treturn (((a ? b : c) ? (d ? e : f) : (g ? h : i)) ? ((j ? k : l) ? (m ? n : o) : (p ? q : r)) : ((s ? t : u) ? (v ? w : x) : (y ? z : A))) == " + str(expr_val) + ";")
|
||||
|
||||
return "\n".join([func_signature, "{"] + func_inner + ["}"])
|
||||
|
||||
if __name__ == "__main__":
|
||||
src = ""
|
||||
return_stmt = "\treturn ("
|
||||
NUM_FUNC = 100
|
||||
for i in range(NUM_FUNC):
|
||||
src += make_func(i)
|
||||
src += "\n\n"
|
||||
return_stmt += "func_" + str(i) + "()"
|
||||
return_stmt += " && " if i != (NUM_FUNC - 1) else ") == "
|
||||
return_stmt += "1;"
|
||||
src += "int main()\n{\n" + return_stmt + "\n}\n"
|
||||
|
||||
print(src)
|
||||
10
examples/simple_cond.c
Normal file
10
examples/simple_cond.c
Normal file
@@ -0,0 +1,10 @@
|
||||
int f(int x) {
|
||||
return x + 8;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int x = 0;
|
||||
int y = (x++ == 1) ? 1 : 2;
|
||||
|
||||
return f((x < y) ? x : 2) == 9;
|
||||
}
|
||||
3
scripts/copy-to-kecc-public.sh
Executable file
3
scripts/copy-to-kecc-public.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
rsync --exclude=".git" --delete --archive ./ ../kecc-public
|
||||
@@ -11,8 +11,9 @@ import sys
|
||||
import re
|
||||
|
||||
if __name__ == "__main__":
|
||||
for fullname in os.listdir("src"):
|
||||
(filename, ext) = os.path.splitext(fullname)
|
||||
for root, dir, files in os.walk("src"):
|
||||
for f in files:
|
||||
(filename, ext) = os.path.splitext(f)
|
||||
|
||||
if ext == ".public":
|
||||
os.rename(os.path.join("src", fullname), os.path.join("src", filename))
|
||||
if ext == ".public":
|
||||
os.rename(os.path.join(root, f), os.path.join(root, filename))
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
mod write_asm;
|
||||
|
||||
/// TODO
|
||||
pub struct Asm {}
|
||||
10
src/asm/write_asm.rs
Normal file
10
src/asm/write_asm.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use std::io::{Result, Write};
|
||||
|
||||
use crate::asm::Asm;
|
||||
use crate::write_base::WriteLine;
|
||||
|
||||
impl WriteLine for Asm {
|
||||
fn write_line(&self, _indent: usize, _write: &mut dyn Write) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@ use crate::ir;
|
||||
use crate::Translate;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Codegen {}
|
||||
pub struct Asmgen {}
|
||||
|
||||
impl Translate<ir::TranslationUnit> for Codegen {
|
||||
impl Translate<ir::TranslationUnit> for Asmgen {
|
||||
type Target = Asm;
|
||||
type Error = ();
|
||||
|
||||
fn translate(&mut self, _source: &ir::TranslationUnit) -> Result<Self::Target, Self::Error> {
|
||||
unimplemented!()
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
use lang_c::ast::*;
|
||||
use lang_c::span::Node;
|
||||
|
||||
use std::ops::Deref;
|
||||
use core::ops::Deref;
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
6
src/c/mod.rs
Normal file
6
src/c/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod ast_equiv;
|
||||
mod parse;
|
||||
mod write_c;
|
||||
|
||||
pub use ast_equiv::assert_ast_equiv;
|
||||
pub use parse::Parse;
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::ops::Deref;
|
||||
use core::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use lang_c::ast::*;
|
||||
@@ -10,6 +10,7 @@ use crate::Translate;
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ParseError(ParseError),
|
||||
#[allow(dead_code)]
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
@@ -101,7 +102,9 @@ impl AssertSupported for FunctionDefinition {
|
||||
impl AssertSupported for DeclarationSpecifier {
|
||||
fn assert_supported(&self) {
|
||||
match self {
|
||||
Self::StorageClass(_) => panic!("DeclarationSpecifier::StorageClass"),
|
||||
Self::StorageClass(storage_class_specifier) => {
|
||||
storage_class_specifier.assert_supported()
|
||||
}
|
||||
Self::TypeSpecifier(type_specifier) => type_specifier.assert_supported(),
|
||||
Self::TypeQualifier(type_qualifier) => type_qualifier.assert_supported(),
|
||||
Self::Function(_) => panic!("DeclarationSpecifier::Function"),
|
||||
@@ -111,6 +114,15 @@ impl AssertSupported for DeclarationSpecifier {
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertSupported for StorageClassSpecifier {
|
||||
fn assert_supported(&self) {
|
||||
match self {
|
||||
Self::Typedef => (),
|
||||
_ => panic!("StorageClassifier other than Typedef"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertSupported for TypeSpecifier {
|
||||
fn assert_supported(&self) {
|
||||
match self {
|
||||
@@ -128,7 +140,7 @@ impl AssertSupported for TypeSpecifier {
|
||||
Self::Atomic(_) => panic!("TypeSpecifier::Atomic"),
|
||||
Self::Struct(struct_type) => struct_type.assert_supported(),
|
||||
Self::Enum(_) => panic!("TypeSpecifier::Enum"),
|
||||
Self::TypedefName(_) => panic!("TypeSpecifier::TypedefName"),
|
||||
Self::TypedefName(_) => (),
|
||||
Self::TypeOf(_) => panic!("TypeSpecifier::TypeOf"),
|
||||
Self::TS18661Float(_) => panic!("TypeSpecifier::TS18661Float"),
|
||||
}
|
||||
@@ -271,7 +283,7 @@ impl AssertSupported for ParameterDeclaration {
|
||||
impl AssertSupported for DeclaratorKind {
|
||||
fn assert_supported(&self) {
|
||||
match self {
|
||||
Self::Abstract => panic!("DeclaratorKind::Abstract"),
|
||||
Self::Abstract => (),
|
||||
Self::Identifier(_) => (),
|
||||
Self::Declarator(decl) => decl.assert_supported(),
|
||||
}
|
||||
@@ -415,8 +427,8 @@ impl AssertSupported for Expression {
|
||||
match self {
|
||||
Self::Identifier(_) => (),
|
||||
Self::Constant(constant) => constant.assert_supported(),
|
||||
Self::StringLiteral(_) => panic!("Expression:StringLiteral"),
|
||||
Self::GenericSelection(_) => panic!("Expression:GenericSelection"),
|
||||
Self::StringLiteral(_) => panic!("Expression::StringLiteral"),
|
||||
Self::GenericSelection(_) => panic!("Expression::GenericSelection"),
|
||||
Self::Member(member) => member.assert_supported(),
|
||||
Self::Call(call) => call.assert_supported(),
|
||||
Self::CompoundLiteral(_) => panic!("Expression::CompoundLiteral"),
|
||||
@@ -531,11 +543,7 @@ impl AssertSupported for FloatFormat {
|
||||
}
|
||||
|
||||
impl AssertSupported for UnaryOperator {
|
||||
fn assert_supported(&self) {
|
||||
if let Self::SizeOf = self {
|
||||
panic!("UnaryOperaotr::SizeOf")
|
||||
}
|
||||
}
|
||||
fn assert_supported(&self) {}
|
||||
}
|
||||
|
||||
impl AssertSupported for BinaryOperator {
|
||||
@@ -1,8 +1,8 @@
|
||||
use lang_c::ast::*;
|
||||
use lang_c::span::Node;
|
||||
|
||||
use core::ops::Deref;
|
||||
use std::io::{Result, Write};
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::write_base::*;
|
||||
|
||||
@@ -45,7 +45,3 @@ impl WriteLine for TranslationUnit {
|
||||
todo!("homework 1")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_c(unit: &TranslationUnit, write: &mut dyn Write) -> Result<()> {
|
||||
unit.write_line(0, write)
|
||||
}
|
||||
612
src/ir/dtype.rs
Normal file
612
src/ir/dtype.rs
Normal file
@@ -0,0 +1,612 @@
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
use core::ops::Deref;
|
||||
use itertools::izip;
|
||||
use lang_c::ast;
|
||||
use lang_c::span::Node;
|
||||
use std::hash::Hash;
|
||||
|
||||
use failure::Fail;
|
||||
|
||||
#[derive(Debug, PartialEq, Fail)]
|
||||
pub enum DtypeError {
|
||||
/// For uncommon error
|
||||
#[fail(display = "{}", message)]
|
||||
Misc { message: String },
|
||||
}
|
||||
|
||||
pub trait HasDtype {
|
||||
fn dtype(&self) -> Dtype;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct BaseDtype {
|
||||
scalar: Option<ast::TypeSpecifier>,
|
||||
signed_option: Option<ast::TypeSpecifier>,
|
||||
is_const: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub enum Dtype {
|
||||
Unit {
|
||||
is_const: bool,
|
||||
},
|
||||
Int {
|
||||
width: usize,
|
||||
is_signed: bool,
|
||||
is_const: bool,
|
||||
},
|
||||
Float {
|
||||
width: usize,
|
||||
is_const: bool,
|
||||
},
|
||||
Pointer {
|
||||
inner: Box<Dtype>,
|
||||
is_const: bool,
|
||||
},
|
||||
Function {
|
||||
ret: Box<Dtype>,
|
||||
params: Vec<Dtype>,
|
||||
},
|
||||
}
|
||||
|
||||
impl BaseDtype {
|
||||
/// Apply `TypeSpecifier` to `BaseDtype`
|
||||
///
|
||||
/// let's say declaration is `const int a;`, if `self` represents `int`
|
||||
/// and `type_specifier` represents `const`, `self` is transformed to
|
||||
/// representing `const int` after function performs.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `self` - Part that has been converted to 'BaseDtype' on the declaration
|
||||
/// * `type_qualifier` - type qualifiers requiring apply to 'self' immediately
|
||||
///
|
||||
#[inline]
|
||||
fn apply_type_specifier(
|
||||
&mut self,
|
||||
type_specifier: &ast::TypeSpecifier,
|
||||
) -> Result<(), DtypeError> {
|
||||
match type_specifier {
|
||||
ast::TypeSpecifier::Unsigned | ast::TypeSpecifier::Signed => {
|
||||
if self.signed_option.is_some() {
|
||||
return Err(DtypeError::Misc {
|
||||
message: "duplicate signed option".to_string(),
|
||||
});
|
||||
}
|
||||
self.signed_option = Some(type_specifier.clone());
|
||||
}
|
||||
ast::TypeSpecifier::Void
|
||||
| ast::TypeSpecifier::Char
|
||||
| ast::TypeSpecifier::Int
|
||||
| ast::TypeSpecifier::Float => {
|
||||
if self.scalar.is_some() {
|
||||
return Err(DtypeError::Misc {
|
||||
message: "two or more scalar types in declaration specifiers".to_string(),
|
||||
});
|
||||
}
|
||||
self.scalar = Some(type_specifier.clone());
|
||||
}
|
||||
_ => todo!("support more like `double` in the future"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply `Typequalifier` to `BaseDtype`
|
||||
///
|
||||
/// let's say declaration is `const int a;`, if `self` represents `int`
|
||||
/// and `type_qualifier` represents `const`, `self` is transformed to
|
||||
/// representing `const int` after function performs.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `self` - Part that has been converted to 'BaseDtype' on the declaration
|
||||
/// * `type_qualifier` - type qualifiers requiring apply to 'self' immediately
|
||||
///
|
||||
#[inline]
|
||||
fn apply_type_qualifier(
|
||||
&mut self,
|
||||
type_qualifier: &ast::TypeQualifier,
|
||||
) -> Result<(), DtypeError> {
|
||||
match type_qualifier {
|
||||
ast::TypeQualifier::Const => {
|
||||
// duplicate `const` is allowed
|
||||
self.is_const = true;
|
||||
}
|
||||
_ => panic!("type qualifier is unsupported except `const`"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_typename_specifier(
|
||||
&mut self,
|
||||
typename_specifier: &ast::SpecifierQualifier,
|
||||
) -> Result<(), DtypeError> {
|
||||
match typename_specifier {
|
||||
ast::SpecifierQualifier::TypeSpecifier(type_specifier) => {
|
||||
self.apply_type_specifier(&type_specifier.node)?
|
||||
}
|
||||
ast::SpecifierQualifier::TypeQualifier(type_qualifier) => {
|
||||
self.apply_type_qualifier(&type_qualifier.node)?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_declaration_specifier(
|
||||
&mut self,
|
||||
declaration_specifier: &ast::DeclarationSpecifier,
|
||||
) -> Result<(), DtypeError> {
|
||||
match declaration_specifier {
|
||||
// TODO: `dtype` must be defined taking into account all specifier information.
|
||||
ast::DeclarationSpecifier::StorageClass(_storage_class_spec) => {
|
||||
todo!("analyze storage class specifier keyword to create correct `dtype`")
|
||||
}
|
||||
ast::DeclarationSpecifier::TypeSpecifier(type_specifier) => {
|
||||
self.apply_type_specifier(&type_specifier.node)?
|
||||
}
|
||||
ast::DeclarationSpecifier::TypeQualifier(type_qualifier) => {
|
||||
self.apply_type_qualifier(&type_qualifier.node)?
|
||||
}
|
||||
_ => panic!("is_unsupported"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply `PointerQualifier` to `BaseDtype`
|
||||
///
|
||||
/// let's say pointer declarator is `* const` of `const int * const a;`.
|
||||
/// If `self` represents nothing, and `pointer_qualifier` represents `const`
|
||||
/// between first and second asterisk, `self` is transformed to
|
||||
/// representing `const` after function performs. This information is used later
|
||||
/// when generating `Dtype`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `self` - Part that has been converted to 'BaseDtype' on the pointer declarator
|
||||
/// * `pointer_qualifier` - Pointer qualifiers requiring apply to 'BaseDtype' immediately
|
||||
///
|
||||
pub fn apply_pointer_qualifier(
|
||||
&mut self,
|
||||
pointer_qualifier: &ast::PointerQualifier,
|
||||
) -> Result<(), DtypeError> {
|
||||
match pointer_qualifier {
|
||||
ast::PointerQualifier::TypeQualifier(type_qualifier) => {
|
||||
self.apply_type_qualifier(&type_qualifier.node)?;
|
||||
}
|
||||
ast::PointerQualifier::Extension(_) => {
|
||||
panic!("ast::PointerQualifier::Extension is unsupported")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_typename_specifiers(
|
||||
&mut self,
|
||||
typename_specifiers: &[Node<ast::SpecifierQualifier>],
|
||||
) -> Result<(), DtypeError> {
|
||||
for ast_spec in typename_specifiers {
|
||||
self.apply_typename_specifier(&ast_spec.node)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_declaration_specifiers(
|
||||
&mut self,
|
||||
declaration_specifiers: &[Node<ast::DeclarationSpecifier>],
|
||||
) -> Result<(), DtypeError> {
|
||||
for ast_spec in declaration_specifiers {
|
||||
self.apply_declaration_specifier(&ast_spec.node)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BaseDtype> for Dtype {
|
||||
type Error = DtypeError;
|
||||
|
||||
/// Derive a data type containing scalar type from specifiers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// For declaration is `const unsigned int * p`, `specifiers` is `const unsigned int`,
|
||||
/// and the result is `Dtype::Int{ width: 32, is_signed: false, is_const: ture }`
|
||||
fn try_from(spec: BaseDtype) -> Result<Self, DtypeError> {
|
||||
assert!(
|
||||
!(spec.scalar.is_none() && spec.signed_option.is_none() && !spec.is_const),
|
||||
"BaseDtype is empty"
|
||||
);
|
||||
|
||||
// Creates `dtype` from scalar.
|
||||
let mut dtype = if let Some(t) = spec.scalar {
|
||||
match t {
|
||||
ast::TypeSpecifier::Void => Self::unit(),
|
||||
ast::TypeSpecifier::Unsigned | ast::TypeSpecifier::Signed => {
|
||||
panic!("Signed option to scalar is not supported")
|
||||
}
|
||||
ast::TypeSpecifier::Bool => Self::BOOL,
|
||||
ast::TypeSpecifier::Char => Self::CHAR,
|
||||
ast::TypeSpecifier::Short => Self::SHORT,
|
||||
ast::TypeSpecifier::Int => Self::INT,
|
||||
ast::TypeSpecifier::Long => Self::LONG,
|
||||
ast::TypeSpecifier::Float => Self::FLOAT,
|
||||
ast::TypeSpecifier::Double => Self::DOUBLE,
|
||||
_ => panic!("Unsupported ast::TypeSpecifier"),
|
||||
}
|
||||
} else {
|
||||
Dtype::default()
|
||||
};
|
||||
|
||||
// Applies signedness.
|
||||
if let Some(signed_option) = spec.signed_option {
|
||||
let is_signed = match signed_option {
|
||||
ast::TypeSpecifier::Signed => true,
|
||||
ast::TypeSpecifier::Unsigned => false,
|
||||
_ => panic!(
|
||||
"`signed_option` must be `TypeSpecifier::Signed` or `TypeSpecifier::Unsigned`"
|
||||
),
|
||||
};
|
||||
|
||||
dtype = dtype.set_signed(is_signed);
|
||||
}
|
||||
|
||||
// Applies constness.
|
||||
assert!(!dtype.is_const());
|
||||
dtype = dtype.set_const(spec.is_const);
|
||||
|
||||
Ok(dtype)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::TypeName> for Dtype {
|
||||
type Error = DtypeError;
|
||||
|
||||
/// Derive a data type from typename.
|
||||
fn try_from(type_name: &ast::TypeName) -> Result<Self, Self::Error> {
|
||||
let mut spec = BaseDtype::default();
|
||||
BaseDtype::apply_typename_specifiers(&mut spec, &type_name.specifiers)?;
|
||||
let mut dtype = Self::try_from(spec)?;
|
||||
|
||||
if let Some(declarator) = &type_name.declarator {
|
||||
dtype = dtype.with_ast_declarator(&declarator.node)?;
|
||||
}
|
||||
Ok(dtype)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::ParameterDeclaration> for Dtype {
|
||||
type Error = DtypeError;
|
||||
|
||||
/// Generate `Dtype` based on parameter declaration
|
||||
fn try_from(parameter_decl: &ast::ParameterDeclaration) -> Result<Self, Self::Error> {
|
||||
let mut spec = BaseDtype::default();
|
||||
BaseDtype::apply_declaration_specifiers(&mut spec, ¶meter_decl.specifiers)?;
|
||||
let mut dtype = Self::try_from(spec)?;
|
||||
|
||||
if let Some(declarator) = ¶meter_decl.declarator {
|
||||
dtype = dtype.with_ast_declarator(&declarator.node)?;
|
||||
}
|
||||
Ok(dtype)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dtype {
|
||||
pub const BOOL: Self = Self::int(1);
|
||||
pub const CHAR: Self = Self::int(8);
|
||||
pub const SHORT: Self = Self::int(16);
|
||||
pub const INT: Self = Self::int(32);
|
||||
pub const LONG: Self = Self::int(64);
|
||||
pub const LONGLONG: Self = Self::int(64);
|
||||
|
||||
pub const FLOAT: Self = Self::float(32);
|
||||
pub const DOUBLE: Self = Self::float(64);
|
||||
|
||||
const WIDTH_OF_BYTE: usize = 8;
|
||||
// TODO: consider architecture dependency in the future
|
||||
const WIDTH_OF_POINTER: usize = 32;
|
||||
|
||||
#[inline]
|
||||
pub const fn unit() -> Self {
|
||||
Self::Unit { is_const: false }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn int(width: usize) -> Self {
|
||||
Self::Int {
|
||||
width,
|
||||
is_signed: true,
|
||||
is_const: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn float(width: usize) -> Self {
|
||||
Self::Float {
|
||||
width,
|
||||
is_const: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pointer(inner: Dtype) -> Self {
|
||||
Self::Pointer {
|
||||
inner: Box::new(inner),
|
||||
is_const: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn function(ret: Dtype, params: Vec<Dtype>) -> Self {
|
||||
Self::Function {
|
||||
ret: Box::new(ret),
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_int_width(&self) -> Option<usize> {
|
||||
if let Self::Int { width, .. } = self {
|
||||
Some(*width)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_float_width(&self) -> Option<usize> {
|
||||
if let Self::Float { width, .. } = self {
|
||||
Some(*width)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_pointer_inner(&self) -> Option<&Dtype> {
|
||||
if let Self::Pointer { inner, .. } = self {
|
||||
Some(inner.deref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_function_inner(&self) -> Option<(&Dtype, &Vec<Dtype>)> {
|
||||
if let Self::Function { ret, params } = self {
|
||||
Some((ret.deref(), params))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_scalar(&self) -> bool {
|
||||
match self {
|
||||
Self::Unit { .. } => todo!(),
|
||||
Self::Int { .. } => true,
|
||||
Self::Float { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_int_signed(&self) -> bool {
|
||||
match self {
|
||||
Self::Int { is_signed, .. } => *is_signed,
|
||||
_ => panic!("only `Dtype::Int` can be judged whether it is sigend"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_const(&self) -> bool {
|
||||
match self {
|
||||
Self::Unit { is_const } => *is_const,
|
||||
Self::Int { is_const, .. } => *is_const,
|
||||
Self::Float { is_const, .. } => *is_const,
|
||||
Self::Pointer { is_const, .. } => *is_const,
|
||||
Self::Function { .. } => {
|
||||
panic!("there should be no case that check whether `Function` is `const`")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_const(self, is_const: bool) -> Self {
|
||||
match self {
|
||||
Self::Unit { .. } => Self::Unit { is_const },
|
||||
Self::Int {
|
||||
width, is_signed, ..
|
||||
} => Self::Int {
|
||||
width,
|
||||
is_signed,
|
||||
is_const,
|
||||
},
|
||||
Self::Float { width, .. } => Self::Float { width, is_const },
|
||||
Self::Pointer { inner, .. } => Self::Pointer { inner, is_const },
|
||||
Self::Function { .. } => panic!("`const` cannot be applied to `Dtype::Function`"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return byte size of `Dtype`
|
||||
pub fn size_of(&self) -> Result<usize, DtypeError> {
|
||||
// TODO: consider complex type like array, structure in the future
|
||||
match self {
|
||||
Self::Unit { .. } => Ok(0),
|
||||
Self::Int { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE),
|
||||
Self::Float { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE),
|
||||
Self::Pointer { .. } => Ok(Self::WIDTH_OF_POINTER / Self::WIDTH_OF_BYTE),
|
||||
Self::Function { .. } => Err(DtypeError::Misc {
|
||||
message: "`sizeof` cannot be used with function types".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return alignment requirements of `Dtype`
|
||||
pub fn align_of(&self) -> Result<usize, DtypeError> {
|
||||
// TODO: consider complex type like array, structure in the future
|
||||
// TODO: when considering complex type like a structure,
|
||||
// the calculation method should be different from `Dtype::size_of`.
|
||||
match self {
|
||||
Self::Unit { .. } => Ok(0),
|
||||
Self::Int { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE),
|
||||
Self::Float { width, .. } => Ok(*width / Self::WIDTH_OF_BYTE),
|
||||
Self::Pointer { .. } => Ok(Self::WIDTH_OF_POINTER / Self::WIDTH_OF_BYTE),
|
||||
Self::Function { .. } => Err(DtypeError::Misc {
|
||||
message: "`alignof` cannot be used with function types".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_signed(self, is_signed: bool) -> Self {
|
||||
match self {
|
||||
Self::Int {
|
||||
width, is_const, ..
|
||||
} => Self::Int {
|
||||
width,
|
||||
is_signed,
|
||||
is_const,
|
||||
},
|
||||
_ => panic!("`signed` and `unsigned` only be applied to `Dtype::Int`"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive a data type from declaration specifiers.
|
||||
pub fn try_from_ast_declaration_specifiers(
|
||||
specifiers: &[Node<ast::DeclarationSpecifier>],
|
||||
) -> Result<Self, DtypeError> {
|
||||
let mut spec = BaseDtype::default();
|
||||
BaseDtype::apply_declaration_specifiers(&mut spec, specifiers)?;
|
||||
Self::try_from(spec)
|
||||
}
|
||||
|
||||
/// Generate `Dtype` based on declarator and `base_dtype` which has scalar type.
|
||||
///
|
||||
/// let's say declaration is `const int * const * const a;`.
|
||||
/// In general `base_dtype` start with `const int` which has scalar type and
|
||||
/// `declarator` represents `* const * const` with `ast::Declarator`
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `declarator` - Parts requiring conversion to 'Dtype' on the declaration
|
||||
/// * `base_dtype` - Part that has been converted to 'Dtype' on the declaration
|
||||
///
|
||||
pub fn with_ast_declarator(mut self, declarator: &ast::Declarator) -> Result<Self, DtypeError> {
|
||||
for derived_decl in &declarator.derived {
|
||||
self = match &derived_decl.node {
|
||||
ast::DerivedDeclarator::Pointer(pointer_qualifiers) => {
|
||||
let mut specifier = BaseDtype::default();
|
||||
for qualifier in pointer_qualifiers {
|
||||
specifier.apply_pointer_qualifier(&qualifier.node)?;
|
||||
}
|
||||
Self::pointer(self).set_const(specifier.is_const)
|
||||
}
|
||||
ast::DerivedDeclarator::Array(_array_decl) => todo!(),
|
||||
ast::DerivedDeclarator::Function(func_decl) => {
|
||||
let params = func_decl
|
||||
.node
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|p| Self::try_from(&p.node))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Self::function(self, params)
|
||||
}
|
||||
ast::DerivedDeclarator::KRFunction(kr_func_decl) => {
|
||||
// K&R function is allowed only when it has no parameter
|
||||
assert!(kr_func_decl.is_empty());
|
||||
Self::function(self, Vec::new())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let declarator_kind = &declarator.kind;
|
||||
match &declarator_kind.node {
|
||||
ast::DeclaratorKind::Abstract => panic!("ast::DeclaratorKind::Abstract is unsupported"),
|
||||
ast::DeclaratorKind::Identifier(_) => Ok(self),
|
||||
ast::DeclaratorKind::Declarator(declarator) => {
|
||||
self.with_ast_declarator(&declarator.node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether type conflict exists between the two `Dtype` objects.
|
||||
///
|
||||
/// let's say expression is `const int a = 0; int b = 0; int c = a + b`.
|
||||
/// Although `const int` of `a` and `int` of `b` looks different, `Plus`(+) operations between
|
||||
/// these two types are possible without any type-casting. There is no conflict between
|
||||
/// `const int` and `int`.
|
||||
///
|
||||
/// However, only the outermost const is ignored.
|
||||
/// If check equivalence between `const int *const` and `int *`, result is false. Because
|
||||
/// the second `const` (means left most `const`) of the `const int *const` is missed in `int *`.
|
||||
/// By the way, outermost `const` (means right most `const`) is not a consideration here.
|
||||
pub fn is_compatible(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Unit { .. }, Self::Unit { .. })
|
||||
| (Self::Int { .. }, Self::Int { .. })
|
||||
| (Self::Float { .. }, Self::Float { .. })
|
||||
| (Self::Pointer { .. }, Self::Pointer { .. }) => {
|
||||
self.clone().set_const(false) == other.clone().set_const(false)
|
||||
}
|
||||
(
|
||||
Self::Function { ret, params },
|
||||
Self::Function {
|
||||
ret: other_ret,
|
||||
params: other_params,
|
||||
},
|
||||
) => {
|
||||
ret == other_ret
|
||||
&& params.len() == other_params.len()
|
||||
&& izip!(params, other_params).all(|(l, r)| l.is_compatible(r))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Dtype {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Unit { is_const } => write!(f, "{}unit", if *is_const { "const " } else { "" }),
|
||||
Self::Int {
|
||||
width,
|
||||
is_signed,
|
||||
is_const,
|
||||
} => write!(
|
||||
f,
|
||||
"{}{}{}",
|
||||
if *is_const { "const " } else { "" },
|
||||
if *is_signed { "i" } else { "u" },
|
||||
width
|
||||
),
|
||||
Self::Float { width, is_const } => {
|
||||
write!(f, "{}f{}", if *is_const { "const " } else { "" }, width)
|
||||
}
|
||||
Self::Pointer { inner, is_const } => {
|
||||
write!(f, "{}* {}", inner, if *is_const { "const" } else { "" })
|
||||
}
|
||||
Self::Function { ret, params } => write!(
|
||||
f,
|
||||
"{} ({})",
|
||||
ret,
|
||||
params
|
||||
.iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Dtype {
|
||||
fn default() -> Self {
|
||||
// default dtype is `int`(i32)
|
||||
Self::INT
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::ir::*;
|
||||
use crate::*;
|
||||
|
||||
use core::fmt;
|
||||
use core::mem;
|
||||
use failure::Fail;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
use crate::ir::*;
|
||||
use crate::*;
|
||||
|
||||
// TODO: the variants of Value will be added in the future
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Value {
|
||||
@@ -74,23 +75,26 @@ pub enum InterpreterError {
|
||||
NoMainFunction,
|
||||
#[fail(display = "ir has no function definition of {} function", func_name)]
|
||||
NoFunctionDefinition { func_name: String },
|
||||
#[fail(
|
||||
display = "{}:{}:{} / Undef value cannot be used as an operand",
|
||||
func_name, bid, iid
|
||||
)]
|
||||
#[fail(display = "{}:{} / {}", func_name, pc, msg)]
|
||||
Misc {
|
||||
func_name: String,
|
||||
bid: BlockId,
|
||||
iid: usize,
|
||||
pc: Pc,
|
||||
msg: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct Pc {
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub struct Pc {
|
||||
pub bid: BlockId,
|
||||
pub iid: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for Pc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.bid, self.iid)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pc {
|
||||
fn new(bid: BlockId) -> Pc {
|
||||
Pc { bid, iid: 0 }
|
||||
@@ -101,16 +105,20 @@ impl Pc {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Default, Debug, PartialEq, Clone)]
|
||||
struct RegisterMap {
|
||||
inner: HashMap<RegisterId, Value>,
|
||||
}
|
||||
|
||||
impl RegisterMap {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: HashMap::new(),
|
||||
}
|
||||
fn read(&self, rid: RegisterId) -> &Value {
|
||||
self.inner
|
||||
.get(&rid)
|
||||
.expect("`rid` must be assigned before it can be used")
|
||||
}
|
||||
|
||||
fn write(&mut self, rid: RegisterId, value: Value) {
|
||||
let _ = self.inner.insert(rid, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +172,7 @@ impl<'i> StackFrame<'i> {
|
||||
fn new(bid: BlockId, func_name: String, func_def: &'i FunctionDefinition) -> Self {
|
||||
StackFrame {
|
||||
pc: Pc::new(bid),
|
||||
registers: RegisterMap::new(),
|
||||
registers: Default::default(),
|
||||
func_name,
|
||||
func_def,
|
||||
}
|
||||
@@ -229,6 +237,39 @@ mod calculator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
struct Memory {
|
||||
// TODO: memory type should change to Vec<Vec<Byte>>
|
||||
inner: Vec<Vec<Value>>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
fn alloc(&mut self, dtype: &Dtype) -> Result<usize, InterpreterError> {
|
||||
let memory_block = match dtype {
|
||||
Dtype::Unit { .. } => vec![],
|
||||
Dtype::Int { width, .. } => match width {
|
||||
32 => vec![Value::Undef],
|
||||
_ => todo!(),
|
||||
},
|
||||
Dtype::Float { .. } => todo!(),
|
||||
Dtype::Pointer { .. } => vec![Value::Undef],
|
||||
Dtype::Function { .. } => vec![],
|
||||
};
|
||||
|
||||
self.inner.push(memory_block);
|
||||
|
||||
Ok(self.inner.len() - 1)
|
||||
}
|
||||
|
||||
fn load(&self, bid: usize, offset: usize) -> &Value {
|
||||
&self.inner[bid][offset]
|
||||
}
|
||||
|
||||
fn store(&mut self, bid: usize, offset: usize, value: Value) {
|
||||
self.inner[bid][offset] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: allocation fields will be added in the future
|
||||
// TODO: program fields will be added in the future
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -238,8 +279,7 @@ struct State<'i> {
|
||||
pub global_map: GlobalMap,
|
||||
pub stack_frame: StackFrame<'i>,
|
||||
pub stack: Vec<StackFrame<'i>>,
|
||||
// TODO: memory type should change to Vec<Vec<Byte>>
|
||||
pub memory: Vec<Vec<Value>>,
|
||||
pub memory: Memory,
|
||||
pub ir: &'i TranslationUnit,
|
||||
}
|
||||
|
||||
@@ -265,35 +305,35 @@ impl<'i> State<'i> {
|
||||
global_map: GlobalMap::default(),
|
||||
stack_frame: StackFrame::new(func_def.bid_init, func_name, func_def),
|
||||
stack: Vec::new(),
|
||||
memory: Vec::new(),
|
||||
memory: Default::default(),
|
||||
ir,
|
||||
};
|
||||
|
||||
state.alloc_global_variable()?;
|
||||
state.alloc_global_variables()?;
|
||||
|
||||
// Initialize state with main function and args
|
||||
state.pass_arguments(args)?;
|
||||
state.alloc_local_variable()?;
|
||||
state.write_args(func_def.bid_init, args)?;
|
||||
state.alloc_local_variables()?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
fn alloc_global_variable(&mut self) -> Result<(), InterpreterError> {
|
||||
fn alloc_global_variables(&mut self) -> Result<(), InterpreterError> {
|
||||
for (name, decl) in &self.ir.decls {
|
||||
// Memory allocation
|
||||
let bid = self.alloc_memory(&decl.dtype())?;
|
||||
let bid = self.memory.alloc(&decl.dtype())?;
|
||||
self.global_map.insert(name.clone(), bid)?;
|
||||
|
||||
// Initialize allocated memory space
|
||||
match decl {
|
||||
Declaration::Variable { dtype, initializer } => {
|
||||
let value = if let Some(constant) = initializer {
|
||||
self.constant_to_value(constant.clone())
|
||||
self.interp_constant(constant.clone())
|
||||
} else {
|
||||
Value::default_from_dtype(dtype)
|
||||
};
|
||||
|
||||
self.memory[bid][0] = value;
|
||||
self.memory.store(bid, 0, value);
|
||||
}
|
||||
// If functin declaration, skip initialization
|
||||
Declaration::Function { .. } => (),
|
||||
@@ -303,46 +343,70 @@ impl<'i> State<'i> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pass_arguments(&mut self, args: Vec<Value>) -> Result<(), InterpreterError> {
|
||||
for (i, value) in args.iter().enumerate() {
|
||||
self.register_write(RegisterId::arg(i), value.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn alloc_local_variable(&mut self) -> Result<(), InterpreterError> {
|
||||
fn alloc_local_variables(&mut self) -> Result<(), InterpreterError> {
|
||||
// add alloc register
|
||||
for (id, allocation) in self.stack_frame.func_def.allocations.iter().enumerate() {
|
||||
let bid = self.alloc_memory(&allocation)?;
|
||||
let bid = self.memory.alloc(&allocation)?;
|
||||
let ptr = Value::pointer(Some(bid), 0);
|
||||
let rid = RegisterId::local("".to_string(), id);
|
||||
|
||||
self.register_write(rid, ptr)
|
||||
self.stack_frame.registers.write(rid, ptr)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn alloc_memory(&mut self, dtype: &Dtype) -> Result<usize, InterpreterError> {
|
||||
// TODO: memory block will be handled as Vec<Byte>
|
||||
let memory_block = match dtype {
|
||||
Dtype::Unit { .. } => vec![],
|
||||
Dtype::Int { width, .. } => match width {
|
||||
32 => vec![Value::Undef],
|
||||
_ => todo!(),
|
||||
},
|
||||
Dtype::Float { .. } => todo!(),
|
||||
Dtype::Pointer { .. } => vec![Value::Undef],
|
||||
Dtype::Function { .. } => vec![],
|
||||
};
|
||||
fn write_args(&mut self, bid_init: BlockId, args: Vec<Value>) -> Result<(), InterpreterError> {
|
||||
for (i, value) in args.iter().enumerate() {
|
||||
self.stack_frame
|
||||
.registers
|
||||
.write(RegisterId::arg(bid_init, i), value.clone());
|
||||
}
|
||||
|
||||
self.memory.push(memory_block);
|
||||
|
||||
Ok(self.memory.len() - 1)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn preprocess_args(
|
||||
fn step(&mut self) -> Result<Option<Value>, InterpreterError> {
|
||||
let block = self
|
||||
.stack_frame
|
||||
.func_def
|
||||
.blocks
|
||||
.get(&self.stack_frame.pc.bid)
|
||||
.expect("block matched with `bid` must be exist");
|
||||
|
||||
// If it's time to execute an instruction, do so.
|
||||
if let Some(instr) = block.instructions.get(self.stack_frame.pc.iid) {
|
||||
self.interp_instruction(instr)?;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Execute a block exit.
|
||||
let return_value = some_or!(self.interp_block_exit(&block.exit)?, return Ok(None));
|
||||
|
||||
// If it's returning from a function, pop the stack frame.
|
||||
|
||||
// TODO: free memory allocated in the callee
|
||||
|
||||
// restore previous state
|
||||
let prev_stack_frame = some_or!(self.stack.pop(), return Ok(Some(return_value)));
|
||||
self.stack_frame = prev_stack_frame;
|
||||
|
||||
// create temporary register to write return value
|
||||
let register = RegisterId::temp(self.stack_frame.pc.bid, self.stack_frame.pc.iid);
|
||||
self.stack_frame.registers.write(register, return_value);
|
||||
self.stack_frame.pc.increment();
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<Value, InterpreterError> {
|
||||
loop {
|
||||
if let Some(value) = self.step()? {
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn interp_args(
|
||||
&self,
|
||||
signature: &FunctionSignature,
|
||||
args: &[Operand],
|
||||
@@ -355,135 +419,111 @@ impl<'i> State<'i> {
|
||||
}
|
||||
|
||||
args.iter()
|
||||
.map(|a| self.get_value(a.clone()))
|
||||
.map(|a| self.interp_operand(a.clone()))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
fn step(&mut self) -> Result<Option<Value>, InterpreterError> {
|
||||
fn interp_jump(&mut self, arg: &JumpArg) -> Result<Option<Value>, InterpreterError> {
|
||||
let block = self
|
||||
.stack_frame
|
||||
.func_def
|
||||
.blocks
|
||||
.get(&self.stack_frame.pc.bid)
|
||||
.expect("block matched with `bid` must be exist");
|
||||
.get(&arg.bid)
|
||||
.expect("block matched with `arg.bid` must be exist");
|
||||
|
||||
if block.instructions.len() == self.stack_frame.pc.iid {
|
||||
self.interpret_block_exit(&block.exit)
|
||||
} else {
|
||||
let instr = block
|
||||
.instructions
|
||||
.get(self.stack_frame.pc.iid)
|
||||
.expect("instruction matched with `iid` must be exist");
|
||||
|
||||
self.interpret_instruction(instr)
|
||||
assert_eq!(arg.args.len(), block.phinodes.len());
|
||||
for (a, d) in izip!(&arg.args, &block.phinodes) {
|
||||
assert!(a.dtype().is_compatible(&d));
|
||||
}
|
||||
|
||||
for (i, a) in arg.args.iter().enumerate() {
|
||||
let v = self.interp_operand(a.clone()).unwrap();
|
||||
self.stack_frame
|
||||
.registers
|
||||
.inner
|
||||
.insert(RegisterId::arg(arg.bid, i), v)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
self.stack_frame.pc = Pc::new(arg.bid);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<Value, InterpreterError> {
|
||||
loop {
|
||||
if let Some(value) = self.step()? {
|
||||
// TODO: Before return, free memory allocated in a function
|
||||
|
||||
// restore previous state
|
||||
let prev_stack_frame = some_or!(self.stack.pop(), {
|
||||
return Ok(value);
|
||||
});
|
||||
self.stack_frame = prev_stack_frame;
|
||||
|
||||
// create temporary register to write return value
|
||||
let register = RegisterId::temp(self.stack_frame.pc.bid, self.stack_frame.pc.iid);
|
||||
self.register_write(register, value);
|
||||
self.stack_frame.pc.increment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn interpret_block_exit(
|
||||
fn interp_block_exit(
|
||||
&mut self,
|
||||
block_exit: &BlockExit,
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
match block_exit {
|
||||
BlockExit::Jump { bid } => {
|
||||
self.stack_frame.pc = Pc::new(*bid);
|
||||
Ok(None)
|
||||
}
|
||||
BlockExit::Jump { arg } => self.interp_jump(arg),
|
||||
BlockExit::ConditionalJump {
|
||||
condition,
|
||||
bid_then,
|
||||
bid_else,
|
||||
arg_then,
|
||||
arg_else,
|
||||
} => {
|
||||
let value = self.get_value(condition.clone())?;
|
||||
let value = self.interp_operand(condition.clone())?;
|
||||
let value = value.get_bool().expect("`condition` must be `Value::Bool`");
|
||||
|
||||
self.stack_frame.pc = Pc::new(if value { *bid_then } else { *bid_else });
|
||||
Ok(None)
|
||||
self.interp_jump(if value { arg_then } else { arg_else })
|
||||
}
|
||||
BlockExit::Switch {
|
||||
value,
|
||||
default,
|
||||
cases,
|
||||
} => {
|
||||
let value = self.get_value(value.clone())?;
|
||||
let value = self.interp_operand(value.clone())?;
|
||||
|
||||
// TODO: consider different integer `width` in the future
|
||||
let bid_next = cases
|
||||
let arg = cases
|
||||
.iter()
|
||||
.find(|(c, _)| value == self.constant_to_value(c.clone()))
|
||||
.map(|(_, bid)| bid)
|
||||
.find(|(c, _)| value == self.interp_constant(c.clone()))
|
||||
.map(|(_, arg)| arg)
|
||||
.unwrap_or_else(|| default);
|
||||
|
||||
self.stack_frame.pc = Pc::new(*bid_next);
|
||||
|
||||
Ok(None)
|
||||
self.interp_jump(arg)
|
||||
}
|
||||
BlockExit::Return { value } => Ok(Some(self.get_value(value.clone())?)),
|
||||
BlockExit::Return { value } => Ok(Some(self.interp_operand(value.clone())?)),
|
||||
BlockExit::Unreachable => Err(InterpreterError::Unreachable),
|
||||
}
|
||||
}
|
||||
|
||||
fn interpret_instruction(
|
||||
&mut self,
|
||||
instruction: &Instruction,
|
||||
) -> Result<Option<Value>, InterpreterError> {
|
||||
fn interp_instruction(&mut self, instruction: &Instruction) -> Result<(), InterpreterError> {
|
||||
let result = match instruction {
|
||||
Instruction::BinOp { op, lhs, rhs, .. } => {
|
||||
let lhs = self.get_value(lhs.clone())?;
|
||||
let rhs = self.get_value(rhs.clone())?;
|
||||
let lhs = self.interp_operand(lhs.clone())?;
|
||||
let rhs = self.interp_operand(rhs.clone())?;
|
||||
|
||||
calculator::calculate_binary_operator_expression(&op, lhs, rhs).map_err(|_| {
|
||||
InterpreterError::Misc {
|
||||
func_name: self.stack_frame.func_name.clone(),
|
||||
bid: self.stack_frame.pc.bid,
|
||||
iid: self.stack_frame.pc.iid,
|
||||
pc: self.stack_frame.pc,
|
||||
msg: "calculate_binary_operator_expression".into(),
|
||||
}
|
||||
})?
|
||||
}
|
||||
Instruction::UnaryOp { op, operand, .. } => {
|
||||
let operand = self.get_value(operand.clone())?;
|
||||
let operand = self.interp_operand(operand.clone())?;
|
||||
|
||||
calculator::calculate_unary_operator_expression(&op, operand).map_err(|_| {
|
||||
InterpreterError::Misc {
|
||||
func_name: self.stack_frame.func_name.clone(),
|
||||
bid: self.stack_frame.pc.bid,
|
||||
iid: self.stack_frame.pc.iid,
|
||||
pc: self.stack_frame.pc,
|
||||
msg: "calculate_unary_operator_expression".into(),
|
||||
}
|
||||
})?
|
||||
}
|
||||
Instruction::Store { ptr, value, .. } => {
|
||||
let ptr = self.get_value(ptr.clone())?;
|
||||
let value = self.get_value(value.clone())?;
|
||||
|
||||
self.memory_store(ptr, value)?;
|
||||
let ptr = self.interp_operand(ptr.clone())?;
|
||||
let value = self.interp_operand(value.clone())?;
|
||||
let (bid, offset) = self.interp_ptr(ptr)?;
|
||||
self.memory.store(bid, offset, value);
|
||||
|
||||
Value::Unit
|
||||
}
|
||||
Instruction::Load { ptr, .. } => {
|
||||
let ptr = self.get_value(ptr.clone())?;
|
||||
|
||||
self.memory_load(ptr)?
|
||||
let ptr = self.interp_operand(ptr.clone())?;
|
||||
let (bid, offset) = self.interp_ptr(ptr)?;
|
||||
self.memory.load(bid, offset).clone()
|
||||
}
|
||||
Instruction::Call { callee, args, .. } => {
|
||||
let ptr = self.get_value(callee.clone())?;
|
||||
let ptr = self.interp_operand(callee.clone())?;
|
||||
|
||||
// Get function name from pointer
|
||||
let (bid, _) = ptr.get_pointer().expect("`ptr` must be `Value::Pointer`");
|
||||
@@ -508,48 +548,50 @@ impl<'i> State<'i> {
|
||||
func_name: callee_name.clone(),
|
||||
})?;
|
||||
|
||||
let args = self.preprocess_args(func_signature, args)?;
|
||||
let args = self.interp_args(func_signature, args)?;
|
||||
|
||||
let stack_frame = StackFrame::new(func_def.bid_init, callee_name, func_def);
|
||||
let prev_stack_frame = mem::replace(&mut self.stack_frame, stack_frame);
|
||||
self.stack.push(prev_stack_frame);
|
||||
|
||||
// Initialize state with function obtained by callee and args
|
||||
self.pass_arguments(args)?;
|
||||
self.alloc_local_variable()?;
|
||||
self.write_args(func_def.bid_init, args)?;
|
||||
self.alloc_local_variables()?;
|
||||
|
||||
return Ok(None);
|
||||
return Ok(());
|
||||
}
|
||||
Instruction::TypeCast {
|
||||
value,
|
||||
target_dtype,
|
||||
} => {
|
||||
let value = self.get_value(value.clone())?;
|
||||
let value = self.interp_operand(value.clone())?;
|
||||
calculator::calculate_typecast(&value, target_dtype).map_err(|_| {
|
||||
InterpreterError::Misc {
|
||||
func_name: self.stack_frame.func_name.clone(),
|
||||
bid: self.stack_frame.pc.bid,
|
||||
iid: self.stack_frame.pc.iid,
|
||||
pc: self.stack_frame.pc,
|
||||
msg: "calculate_typecast".into(),
|
||||
}
|
||||
})?
|
||||
}
|
||||
};
|
||||
|
||||
let register = RegisterId::temp(self.stack_frame.pc.bid, self.stack_frame.pc.iid);
|
||||
self.register_write(register, result);
|
||||
self.stack_frame.registers.write(register, result);
|
||||
self.stack_frame.pc.increment();
|
||||
|
||||
Ok(None)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_value(&self, operand: Operand) -> Result<Value, InterpreterError> {
|
||||
fn interp_operand(&self, operand: Operand) -> Result<Value, InterpreterError> {
|
||||
match &operand {
|
||||
Operand::Constant(value) => Ok(self.constant_to_value(value.clone())),
|
||||
Operand::Register { rid, .. } => Ok(self.register_read(rid.clone())),
|
||||
Operand::Constant(value) => Ok(self.interp_constant(value.clone())),
|
||||
Operand::Register { rid, .. } => {
|
||||
Ok(self.stack_frame.registers.read(rid.clone()).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn constant_to_value(&self, value: Constant) -> Value {
|
||||
fn interp_constant(&self, value: Constant) -> Value {
|
||||
match value {
|
||||
Constant::Unit => Value::Unit,
|
||||
// TODO: consider `width` and `is_signed` in the future
|
||||
@@ -570,43 +612,27 @@ impl<'i> State<'i> {
|
||||
}
|
||||
}
|
||||
|
||||
fn register_write(&mut self, rid: RegisterId, value: Value) {
|
||||
let _ = self.stack_frame.registers.inner.insert(rid, value);
|
||||
}
|
||||
|
||||
fn register_read(&self, rid: RegisterId) -> Value {
|
||||
self.stack_frame
|
||||
.registers
|
||||
.inner
|
||||
.get(&rid)
|
||||
.cloned()
|
||||
.expect("`rid` must be assigned before it can be used")
|
||||
}
|
||||
|
||||
fn memory_store(&mut self, pointer: Value, value: Value) -> Result<(), InterpreterError> {
|
||||
fn interp_ptr(&mut self, pointer: Value) -> Result<(usize, usize), InterpreterError> {
|
||||
let (bid, offset) = pointer
|
||||
.get_pointer()
|
||||
.expect("`pointer` must be `Value::Pointer` to access memory");
|
||||
.ok_or_else(|| InterpreterError::Misc {
|
||||
func_name: self.stack_frame.func_name.clone(),
|
||||
pc: self.stack_frame.pc,
|
||||
msg: "Accessing memory with non-pointer".into(),
|
||||
})?;
|
||||
|
||||
let bid = bid.expect("write to memory using constant value address is not allowed");
|
||||
self.memory[bid][offset] = value;
|
||||
let bid = bid.ok_or_else(|| InterpreterError::Misc {
|
||||
func_name: self.stack_frame.func_name.clone(),
|
||||
pc: self.stack_frame.pc,
|
||||
msg: "Accessing memory with constant pointer".into(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn memory_load(&self, pointer: Value) -> Result<Value, InterpreterError> {
|
||||
let (bid, offset) = pointer
|
||||
.get_pointer()
|
||||
.expect("`pointer` must be `Value::Pointer` to access memory");
|
||||
|
||||
let bid = bid.expect("read from memory using constant value address is not allowed");
|
||||
|
||||
Ok(self.memory[bid][offset].clone())
|
||||
Ok((bid, offset))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn run_ir(ir: &TranslationUnit, args: Vec<Value>) -> Result<Value, InterpreterError> {
|
||||
pub fn interp(ir: &TranslationUnit, args: Vec<Value>) -> Result<Value, InterpreterError> {
|
||||
let mut init_state = State::new(ir, args)?;
|
||||
init_state.run()
|
||||
}
|
||||
578
src/ir/mod.rs
Normal file
578
src/ir/mod.rs
Normal file
@@ -0,0 +1,578 @@
|
||||
mod dtype;
|
||||
mod interp;
|
||||
mod write_ir;
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
use core::ops::Deref;
|
||||
use lang_c::ast;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
pub use dtype::{Dtype, DtypeError, HasDtype};
|
||||
pub use interp::{interp, Value};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct TranslationUnit {
|
||||
pub decls: HashMap<String, Declaration>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Declaration {
|
||||
Variable {
|
||||
dtype: Dtype,
|
||||
initializer: Option<Constant>,
|
||||
},
|
||||
Function {
|
||||
signature: FunctionSignature,
|
||||
definition: Option<FunctionDefinition>,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<Dtype> for Declaration {
|
||||
type Error = DtypeError;
|
||||
|
||||
/// Create an appropriate declaration according to `dtype`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// If `int g = 0;` is declared, `dtype` is
|
||||
/// `ir::Dtype::Int{ width:32, is_signed:true, is_const:false }`.
|
||||
/// In this case, `ir::Declaration::Variable{ dtype, initializer: Some(Constant::I32(1)) }`
|
||||
/// is generated.
|
||||
///
|
||||
/// Conversely, if `int foo();` is declared, `dtype` is
|
||||
/// `ir::Dtype::Function{ret: Scalar(Int), params: []}`.
|
||||
/// Thus, in this case, `ir::Declaration::Function` is generated.
|
||||
fn try_from(dtype: Dtype) -> Result<Self, Self::Error> {
|
||||
match &dtype {
|
||||
Dtype::Unit { .. } => Err(DtypeError::Misc {
|
||||
message: "A variable of type `void` cannot be declared".to_string(),
|
||||
}),
|
||||
Dtype::Int { .. } | Dtype::Float { .. } | Dtype::Pointer { .. } => {
|
||||
Ok(Declaration::Variable {
|
||||
dtype,
|
||||
initializer: None,
|
||||
})
|
||||
}
|
||||
Dtype::Function { .. } => Ok(Declaration::Function {
|
||||
signature: FunctionSignature::new(dtype),
|
||||
definition: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Declaration {
|
||||
pub fn get_variable(&self) -> Option<(&Dtype, &Option<Constant>)> {
|
||||
if let Self::Variable { dtype, initializer } = self {
|
||||
Some((dtype, initializer))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_function(&self) -> Option<(&FunctionSignature, &Option<FunctionDefinition>)> {
|
||||
if let Self::Function {
|
||||
signature,
|
||||
definition,
|
||||
} = self
|
||||
{
|
||||
Some((signature, definition))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_function_mut(
|
||||
&mut self,
|
||||
) -> Option<(&mut FunctionSignature, &mut Option<FunctionDefinition>)> {
|
||||
if let Self::Function {
|
||||
signature,
|
||||
definition,
|
||||
} = self
|
||||
{
|
||||
Some((signature, definition))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if type is conflicting for pre-declared one
|
||||
///
|
||||
/// In case of `Variable`, need to check if the two types are exactly the same.
|
||||
/// On the other hand, in the case of `Function`, outermost `const` of return type and
|
||||
/// parameters one is not an issue of concern.
|
||||
pub fn is_compatible(&self, other: &Declaration) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Variable { dtype, .. }, Self::Variable { dtype: other, .. }) => dtype == other,
|
||||
(
|
||||
Self::Function { signature, .. },
|
||||
Self::Function {
|
||||
signature: other, ..
|
||||
},
|
||||
) => signature.dtype().is_compatible(&other.dtype()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDtype for Declaration {
|
||||
fn dtype(&self) -> Dtype {
|
||||
match self {
|
||||
Self::Variable { dtype, .. } => dtype.clone(),
|
||||
Self::Function { signature, .. } => signature.dtype(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct FunctionDefinition {
|
||||
/// Memory allocations for local variables. The allocation is performed at the beginning of a
|
||||
/// function invocation.
|
||||
pub allocations: Vec<Dtype>,
|
||||
|
||||
/// Basic blocks.
|
||||
pub blocks: HashMap<BlockId, Block>,
|
||||
|
||||
/// The initial block id.
|
||||
pub bid_init: BlockId,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct FunctionSignature {
|
||||
pub ret: Dtype,
|
||||
pub params: Vec<Dtype>,
|
||||
}
|
||||
|
||||
impl FunctionSignature {
|
||||
pub fn new(dtype: Dtype) -> Self {
|
||||
let (ret, params) = dtype
|
||||
.get_function_inner()
|
||||
.expect("function signature's dtype must be function type");
|
||||
Self {
|
||||
ret: ret.clone(),
|
||||
params: params.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDtype for FunctionSignature {
|
||||
fn dtype(&self) -> Dtype {
|
||||
Dtype::function(self.ret.clone(), self.params.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Clone)]
|
||||
pub enum RegisterId {
|
||||
/// Registers holding pointers to local allocations.
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// - `name`: only for debugging purposes.
|
||||
/// - `id`: local allocation id.
|
||||
Local { name: String, id: usize },
|
||||
|
||||
/// Registers holding block arguments.
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// - `bid`: When it is the initial block id, then it holds a function argument; otherwise, it
|
||||
/// holds a phinode value.
|
||||
/// - `aid`: the argument index.
|
||||
Arg { bid: BlockId, aid: usize },
|
||||
|
||||
/// Registers holding the results of instructions.
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// - `bid`: the instruction's block id.
|
||||
/// - `iid`: the instruction's id in the block.
|
||||
Temp { bid: BlockId, iid: usize },
|
||||
}
|
||||
|
||||
impl RegisterId {
|
||||
pub fn local(name: String, id: usize) -> Self {
|
||||
Self::Local { name, id }
|
||||
}
|
||||
|
||||
pub fn arg(bid: BlockId, aid: usize) -> Self {
|
||||
Self::Arg { bid, aid }
|
||||
}
|
||||
|
||||
pub fn temp(bid: BlockId, iid: usize) -> Self {
|
||||
Self::Temp { bid, iid }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RegisterId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Local { name, id } => write!(f, "%(local:{}:{})", name, id),
|
||||
Self::Arg { bid, aid } => write!(f, "%(arg:{}:{})", bid, aid),
|
||||
Self::Temp { bid, iid } => write!(f, "%({}:{})", bid, iid),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<RegisterId> for RegisterId {
|
||||
fn eq(&self, other: &RegisterId) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Local { id, .. }, Self::Local { id: other_id, .. }) => id == other_id,
|
||||
(
|
||||
Self::Arg { bid, aid },
|
||||
Self::Arg {
|
||||
bid: other_bid,
|
||||
aid: other_aid,
|
||||
},
|
||||
) => bid == other_bid && aid == other_aid,
|
||||
(
|
||||
Self::Temp { bid, iid },
|
||||
Self::Temp {
|
||||
bid: other_bid,
|
||||
iid: other_iid,
|
||||
},
|
||||
) => bid == other_bid && iid == other_iid,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for RegisterId {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Self::Local { id, .. } => id.hash(state),
|
||||
Self::Arg { bid, aid } => {
|
||||
// TODO: needs to distinguish arg/temp?
|
||||
bid.hash(state);
|
||||
aid.hash(state);
|
||||
}
|
||||
Self::Temp { bid, iid } => {
|
||||
bid.hash(state);
|
||||
iid.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Constant {
|
||||
Unit,
|
||||
Int {
|
||||
value: u128,
|
||||
width: usize,
|
||||
is_signed: bool,
|
||||
},
|
||||
Float {
|
||||
/// `value` may be `f32`, but it is possible to consider it as `f64`.
|
||||
///
|
||||
/// * Casting from an f32 to an f64 is perfect and lossless (f32 -> f64)
|
||||
/// * Casting from an f64 to an f32 will produce the closest possible value (f64 -> f32)
|
||||
/// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#type-cast-expressions
|
||||
value: f64,
|
||||
width: usize,
|
||||
},
|
||||
GlobalVariable {
|
||||
name: String,
|
||||
dtype: Dtype,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::Constant> for Constant {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(constant: &ast::Constant) -> Result<Self, Self::Error> {
|
||||
match constant {
|
||||
ast::Constant::Integer(integer) => {
|
||||
let is_signed = !integer.suffix.unsigned;
|
||||
|
||||
let dtype = match integer.suffix.size {
|
||||
ast::IntegerSize::Int => Dtype::INT,
|
||||
ast::IntegerSize::Long => Dtype::LONG,
|
||||
ast::IntegerSize::LongLong => Dtype::LONGLONG,
|
||||
}
|
||||
.set_signed(is_signed);
|
||||
|
||||
let value = if is_signed {
|
||||
integer.number.parse::<i128>().unwrap() as u128
|
||||
} else {
|
||||
integer.number.parse::<u128>().unwrap()
|
||||
};
|
||||
|
||||
Ok(Self::int(value, dtype))
|
||||
}
|
||||
ast::Constant::Float(float) => {
|
||||
let (dtype, value) = match float.suffix.format {
|
||||
ast::FloatFormat::Float => {
|
||||
// Casting from an f32 to an f64 is perfect and lossless (f32 -> f64)
|
||||
// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#type-cast-expressions
|
||||
(Dtype::FLOAT, float.number.parse::<f32>().unwrap() as f64)
|
||||
}
|
||||
ast::FloatFormat::Double => {
|
||||
(Dtype::DOUBLE, float.number.parse::<f64>().unwrap())
|
||||
}
|
||||
ast::FloatFormat::LongDouble => {
|
||||
panic!("`FloatFormat::LongDouble` is_unsupported")
|
||||
}
|
||||
ast::FloatFormat::TS18661Format(_) => {
|
||||
panic!("`FloatFormat::TS18661Format` is_unsupported")
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self::float(value, dtype))
|
||||
}
|
||||
ast::Constant::Character(character) => {
|
||||
let dtype = Dtype::CHAR;
|
||||
let value = character.parse::<char>().unwrap() as u128;
|
||||
|
||||
Ok(Self::int(value, dtype))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::Expression> for Constant {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(expr: &ast::Expression) -> Result<Self, Self::Error> {
|
||||
if let ast::Expression::Constant(constant) = expr {
|
||||
Self::try_from(&constant.node)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::Initializer> for Constant {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(initializer: &ast::Initializer) -> Result<Self, Self::Error> {
|
||||
if let ast::Initializer::Expression(expr) = &initializer {
|
||||
Self::try_from(&expr.node)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Constant {
|
||||
#[inline]
|
||||
pub fn is_integer_constant(&self) -> bool {
|
||||
if let Self::Int { .. } = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unit() -> Self {
|
||||
Constant::Unit
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn int(value: u128, dtype: Dtype) -> Self {
|
||||
let width = dtype.get_int_width().expect("`dtype` must be `Dtype::Int`");
|
||||
let is_signed = dtype.is_int_signed();
|
||||
|
||||
Constant::Int {
|
||||
value,
|
||||
width,
|
||||
is_signed,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn float(value: f64, dtype: Dtype) -> Self {
|
||||
let width = dtype
|
||||
.get_float_width()
|
||||
.expect("`dtype` must be `Dtype::Float`");
|
||||
|
||||
Constant::Float { value, width }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn global_variable(name: String, dtype: Dtype) -> Self {
|
||||
Self::GlobalVariable { name, dtype }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Constant {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Unit => write!(f, "unit"),
|
||||
Self::Int { value, .. } => write!(f, "{}", value),
|
||||
Self::Float { value, .. } => write!(f, "{}", value),
|
||||
Self::GlobalVariable { name, .. } => write!(f, "%{}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDtype for Constant {
|
||||
fn dtype(&self) -> Dtype {
|
||||
match self {
|
||||
Self::Unit => Dtype::unit(),
|
||||
Self::Int {
|
||||
width, is_signed, ..
|
||||
} => Dtype::int(*width).set_signed(*is_signed),
|
||||
Self::Float { width, .. } => Dtype::float(*width),
|
||||
Self::GlobalVariable { dtype, .. } => Dtype::pointer(dtype.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Operand {
|
||||
Constant(Constant),
|
||||
Register { rid: RegisterId, dtype: Dtype },
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
pub fn constant(value: Constant) -> Self {
|
||||
Self::Constant(value)
|
||||
}
|
||||
|
||||
pub fn register(rid: RegisterId, dtype: Dtype) -> Self {
|
||||
Self::Register { rid, dtype }
|
||||
}
|
||||
|
||||
pub fn get_constant(&self) -> Option<&Constant> {
|
||||
if let Self::Constant(constant) = self {
|
||||
Some(constant)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register(&self) -> Option<(&RegisterId, &Dtype)> {
|
||||
if let Self::Register { rid, dtype } = self {
|
||||
Some((rid, dtype))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Operand {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Constant(value) => write!(f, "{}", value),
|
||||
Self::Register { rid, .. } => write!(f, "{}", rid),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDtype for Operand {
|
||||
fn dtype(&self) -> Dtype {
|
||||
match self {
|
||||
Self::Constant(value) => value.dtype(),
|
||||
Self::Register { dtype, .. } => dtype.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Instruction {
|
||||
// TODO: the variants of Instruction will be added in the future
|
||||
BinOp {
|
||||
op: ast::BinaryOperator,
|
||||
lhs: Operand,
|
||||
rhs: Operand,
|
||||
dtype: Dtype,
|
||||
},
|
||||
UnaryOp {
|
||||
op: ast::UnaryOperator,
|
||||
operand: Operand,
|
||||
dtype: Dtype,
|
||||
},
|
||||
Store {
|
||||
ptr: Operand,
|
||||
value: Operand,
|
||||
},
|
||||
Load {
|
||||
ptr: Operand,
|
||||
},
|
||||
Call {
|
||||
callee: Operand,
|
||||
args: Vec<Operand>,
|
||||
return_type: Dtype,
|
||||
},
|
||||
TypeCast {
|
||||
value: Operand,
|
||||
target_dtype: Dtype,
|
||||
},
|
||||
}
|
||||
|
||||
impl HasDtype for Instruction {
|
||||
fn dtype(&self) -> Dtype {
|
||||
match self {
|
||||
Self::BinOp { dtype, .. } => dtype.clone(),
|
||||
Self::UnaryOp { dtype, .. } => dtype.clone(),
|
||||
Self::Store { .. } => Dtype::unit(),
|
||||
Self::Load { ptr } => ptr
|
||||
.dtype()
|
||||
.get_pointer_inner()
|
||||
.expect("Load instruction must have pointer value as operand")
|
||||
.deref()
|
||||
.clone()
|
||||
.set_const(false),
|
||||
Self::Call { return_type, .. } => return_type.clone(),
|
||||
Self::TypeCast { target_dtype, .. } => target_dtype.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub struct BlockId(pub usize);
|
||||
|
||||
impl fmt::Display for BlockId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "b{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct JumpArg {
|
||||
pub bid: BlockId,
|
||||
pub args: Vec<Operand>,
|
||||
}
|
||||
|
||||
impl JumpArg {
|
||||
pub fn new(bid: BlockId, args: Vec<Operand>) -> Self {
|
||||
Self { bid, args }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for JumpArg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}({:?})", self.bid, self.args)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BlockExit {
|
||||
Jump {
|
||||
arg: JumpArg,
|
||||
},
|
||||
ConditionalJump {
|
||||
condition: Operand,
|
||||
arg_then: JumpArg,
|
||||
arg_else: JumpArg,
|
||||
},
|
||||
Switch {
|
||||
value: Operand,
|
||||
default: JumpArg,
|
||||
cases: Vec<(Constant, JumpArg)>,
|
||||
},
|
||||
Return {
|
||||
value: Operand,
|
||||
},
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Block {
|
||||
pub phinodes: Vec<Dtype>,
|
||||
pub instructions: Vec<Instruction>,
|
||||
pub exit: BlockExit,
|
||||
}
|
||||
@@ -188,16 +188,16 @@ impl WriteOp for ast::UnaryOperator {
|
||||
impl WriteString for BlockExit {
|
||||
fn write_string(&self) -> String {
|
||||
match self {
|
||||
BlockExit::Jump { bid } => format!("j {}", bid),
|
||||
BlockExit::Jump { arg } => format!("j {}", arg),
|
||||
BlockExit::ConditionalJump {
|
||||
condition,
|
||||
bid_then,
|
||||
bid_else,
|
||||
arg_then,
|
||||
arg_else,
|
||||
} => format!(
|
||||
"br {}, {}, {}",
|
||||
condition.write_string(),
|
||||
bid_then,
|
||||
bid_else
|
||||
arg_then,
|
||||
arg_else
|
||||
),
|
||||
BlockExit::Switch {
|
||||
value,
|
||||
@@ -218,7 +218,3 @@ impl WriteString for BlockExit {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_ir(ir: &TranslationUnit, write: &mut dyn Write) -> Result<()> {
|
||||
ir.write_line(0, write)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fmt;
|
||||
use core::fmt;
|
||||
|
||||
use lang_c::ast::*;
|
||||
|
||||
41
src/lib.rs
41
src/lib.rs
@@ -1,37 +1,24 @@
|
||||
#![deny(warnings)]
|
||||
#![allow(unreachable_code)]
|
||||
|
||||
mod tests;
|
||||
mod utils;
|
||||
|
||||
pub mod asm;
|
||||
pub mod ir;
|
||||
|
||||
mod codegen;
|
||||
mod irgen;
|
||||
mod optimize;
|
||||
mod parse;
|
||||
|
||||
pub mod run_ir;
|
||||
mod write_asm;
|
||||
mod write_base;
|
||||
mod write_c;
|
||||
mod write_ir;
|
||||
|
||||
pub mod assert_ast_equiv;
|
||||
pub mod write_c_test;
|
||||
mod asm;
|
||||
mod c;
|
||||
mod ir;
|
||||
|
||||
mod asmgen;
|
||||
mod irgen;
|
||||
mod opt;
|
||||
|
||||
pub use tests::*;
|
||||
pub use utils::*;
|
||||
pub use write_base::write;
|
||||
|
||||
pub use asm::Asm;
|
||||
pub use c::Parse;
|
||||
|
||||
pub use codegen::Codegen;
|
||||
pub use asmgen::Asmgen;
|
||||
pub use irgen::Irgen;
|
||||
pub use optimize::{O0, O1};
|
||||
pub use parse::Parse;
|
||||
pub use utils::{Optimize, Repeat, Translate};
|
||||
|
||||
pub use write_asm::write_asm;
|
||||
pub use write_c::write_c;
|
||||
pub use write_ir::write_ir;
|
||||
|
||||
pub use assert_ast_equiv::assert_ast_equiv;
|
||||
pub use write_c_test::write_c_test;
|
||||
pub use opt::{Gvn, Mem2reg, Optimize, Repeat, SimplifyCfg, Translate, O0, O1};
|
||||
|
||||
11
src/opt/gvn.rs
Normal file
11
src/opt/gvn.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::ir;
|
||||
use crate::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Gvn {}
|
||||
|
||||
impl Optimize<ir::TranslationUnit> for Gvn {
|
||||
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
11
src/opt/mem2reg.rs
Normal file
11
src/opt/mem2reg.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::ir;
|
||||
use crate::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Mem2reg {}
|
||||
|
||||
impl Optimize<ir::TranslationUnit> for Mem2reg {
|
||||
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
55
src/opt/mod.rs
Normal file
55
src/opt/mod.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
mod gvn;
|
||||
mod mem2reg;
|
||||
mod simplify_cfg;
|
||||
|
||||
pub use gvn::Gvn;
|
||||
pub use mem2reg::Mem2reg;
|
||||
pub use simplify_cfg::SimplifyCfg;
|
||||
|
||||
use crate::ir;
|
||||
|
||||
pub trait Translate<S> {
|
||||
type Target;
|
||||
type Error;
|
||||
|
||||
fn translate(&mut self, source: &S) -> Result<Self::Target, Self::Error>;
|
||||
}
|
||||
|
||||
pub trait Optimize<T> {
|
||||
fn optimize(&mut self, code: &mut T) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Repeat<O> {
|
||||
inner: O,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct O0 {}
|
||||
|
||||
pub type O1 = Repeat<(SimplifyCfg, (Mem2reg, Gvn))>;
|
||||
|
||||
impl Optimize<ir::TranslationUnit> for O0 {
|
||||
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, O1: Optimize<T>, O2: Optimize<T>> Optimize<T> for (O1, O2) {
|
||||
fn optimize(&mut self, code: &mut T) -> bool {
|
||||
let changed1 = self.0.optimize(code);
|
||||
let changed2 = self.1.optimize(code);
|
||||
changed1 || changed2
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, O: Optimize<T>> Optimize<T> for Repeat<O> {
|
||||
fn optimize(&mut self, code: &mut T) -> bool {
|
||||
if !self.inner.optimize(code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while self.inner.optimize(code) {}
|
||||
true
|
||||
}
|
||||
}
|
||||
48
src/opt/simplify_cfg.rs
Normal file
48
src/opt/simplify_cfg.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use crate::ir::*;
|
||||
use crate::*;
|
||||
|
||||
pub type SimplifyCfg = Repeat<(SimplifyCfgConstProp, (SimplifyCfgReach, SimplifyCfgMerge))>;
|
||||
|
||||
impl Optimize<TranslationUnit> for SimplifyCfg {
|
||||
fn optimize(&mut self, code: &mut TranslationUnit) -> bool {
|
||||
code.decls.iter_mut().any(|(_, decl)| self.optimize(decl))
|
||||
}
|
||||
}
|
||||
|
||||
impl Optimize<Declaration> for SimplifyCfg {
|
||||
fn optimize(&mut self, code: &mut Declaration) -> bool {
|
||||
let (_fsig, fdef) = some_or!(code.get_function_mut(), return false);
|
||||
let fdef = some_or!(fdef, return false);
|
||||
self.optimize(fdef)
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplifies block exits by propagating constants.
|
||||
#[derive(Default)]
|
||||
pub struct SimplifyCfgConstProp {}
|
||||
|
||||
/// Retains only those blocks that are reachable from the init.
|
||||
#[derive(Default)]
|
||||
pub struct SimplifyCfgReach {}
|
||||
|
||||
/// Merges two blocks if a block is pointed to only by another
|
||||
#[derive(Default)]
|
||||
pub struct SimplifyCfgMerge {}
|
||||
|
||||
impl Optimize<FunctionDefinition> for SimplifyCfgConstProp {
|
||||
fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool {
|
||||
todo!("homework 3")
|
||||
}
|
||||
}
|
||||
|
||||
impl Optimize<FunctionDefinition> for SimplifyCfgReach {
|
||||
fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool {
|
||||
todo!("homework 3")
|
||||
}
|
||||
}
|
||||
|
||||
impl Optimize<FunctionDefinition> for SimplifyCfgMerge {
|
||||
fn optimize(&mut self, _code: &mut FunctionDefinition) -> bool {
|
||||
todo!("homework 3")
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use crate::ir;
|
||||
use crate::{Optimize, Repeat};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct O0 {}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Mem2reg {}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Gvn {}
|
||||
|
||||
pub type O1 = Repeat<(Mem2reg, Gvn)>;
|
||||
|
||||
impl Optimize<ir::TranslationUnit> for O0 {
|
||||
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Optimize<ir::TranslationUnit> for Mem2reg {
|
||||
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Optimize<ir::TranslationUnit> for Gvn {
|
||||
fn optimize(&mut self, _code: &mut ir::TranslationUnit) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
56
src/tests.rs
Normal file
56
src/tests.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use lang_c::ast::*;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub fn test_write_c(unit: &TranslationUnit, _path: &Path) {
|
||||
let temp_dir = tempdir().expect("temp dir creation failed");
|
||||
let temp_file_path = temp_dir.path().join("temp.c");
|
||||
let mut temp_file = File::create(&temp_file_path).unwrap();
|
||||
|
||||
crate::write(unit, &mut temp_file).unwrap();
|
||||
|
||||
let new_unit = c::Parse::default()
|
||||
.translate(&temp_file_path.as_path())
|
||||
.expect("parse failed while parsing the output from implemented printer");
|
||||
drop(temp_file);
|
||||
c::assert_ast_equiv(&unit, &new_unit);
|
||||
temp_dir.close().expect("temp dir deletion failed");
|
||||
}
|
||||
|
||||
pub fn test_irgen(unit: &TranslationUnit, path: &Path) {
|
||||
// Check if the file has .c extension
|
||||
assert_eq!(path.extension(), Some(std::ffi::OsStr::new("c")));
|
||||
|
||||
let file_path = path.display().to_string();
|
||||
let bin_path = path.with_extension("exe").as_path().display().to_string();
|
||||
|
||||
// Compile c file
|
||||
Command::new("gcc")
|
||||
.args(&["-O1", &file_path, "-o", &bin_path])
|
||||
.output()
|
||||
.expect("failed to compile the given program");
|
||||
|
||||
// Execute compiled executable
|
||||
let status = Command::new(bin_path.clone())
|
||||
.status()
|
||||
.expect("failed to execute the compiled executable")
|
||||
.code()
|
||||
.expect("failed to return an exit code");
|
||||
|
||||
// Remove compiled executable
|
||||
Command::new("rm")
|
||||
.arg(bin_path)
|
||||
.status()
|
||||
.expect("failed to remove compiled executable");
|
||||
|
||||
let ir = Irgen::default()
|
||||
.translate(unit)
|
||||
.expect("failed to generate ir");
|
||||
|
||||
let args = Vec::new();
|
||||
assert_eq!(ir::interp(&ir, args), Ok(ir::Value::Int(status)));
|
||||
}
|
||||
35
src/utils.rs
35
src/utils.rs
@@ -44,38 +44,3 @@ macro_rules! some_or_exit {
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub trait Translate<S> {
|
||||
type Target;
|
||||
type Error;
|
||||
|
||||
fn translate(&mut self, source: &S) -> Result<Self::Target, Self::Error>;
|
||||
}
|
||||
|
||||
pub trait Optimize<T> {
|
||||
fn optimize(&mut self, code: &mut T) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Repeat<O> {
|
||||
inner: O,
|
||||
}
|
||||
|
||||
impl<T, O1: Optimize<T>, O2: Optimize<T>> Optimize<T> for (O1, O2) {
|
||||
fn optimize(&mut self, code: &mut T) -> bool {
|
||||
let changed1 = self.0.optimize(code);
|
||||
let changed2 = self.1.optimize(code);
|
||||
changed1 || changed2
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, O: Optimize<T>> Optimize<T> for Repeat<O> {
|
||||
fn optimize(&mut self, code: &mut T) -> bool {
|
||||
if !self.inner.optimize(code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while self.inner.optimize(code) {}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
use crate::asm::Asm;
|
||||
|
||||
pub fn write_asm(_asm: &Asm, _write: &mut dyn ::std::io::Write) {
|
||||
unimplemented!();
|
||||
}
|
||||
@@ -16,3 +16,7 @@ pub trait WriteString {
|
||||
pub trait WriteOp {
|
||||
fn write_operation(&self) -> String;
|
||||
}
|
||||
|
||||
pub fn write<T: WriteLine>(t: &T, write: &mut dyn Write) -> Result<()> {
|
||||
t.write_line(0, write)
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
use lang_c::ast::*;
|
||||
use std::fs::File;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub fn write_c_test(unit: &TranslationUnit) {
|
||||
let temp_dir = tempdir().expect("temp dir creation failed");
|
||||
let temp_file_path = temp_dir.path().join("temp.c");
|
||||
let mut temp_file = File::create(&temp_file_path).unwrap();
|
||||
|
||||
write_c(&unit, &mut temp_file).unwrap();
|
||||
|
||||
let new_unit = Parse::default()
|
||||
.translate(&temp_file_path.as_path())
|
||||
.expect("parse failed while parsing file from implemented printer");
|
||||
drop(temp_file);
|
||||
assert_ast_equiv(&unit, &new_unit);
|
||||
temp_dir.close().expect("temp dir deletion failed");
|
||||
}
|
||||
2
tests/.gitignore
vendored
2
tests/.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
/platform.info
|
||||
/csmith-*
|
||||
/test*.c*
|
||||
/reduce-criteria.sh
|
||||
|
||||
232
tests/fuzz.py
232
tests/fuzz.py
@@ -13,30 +13,71 @@ import itertools
|
||||
import argparse
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
REPLACE_DICT = {
|
||||
"#include \"csmith.h\"": "",
|
||||
"volatile ": "",
|
||||
"uint16_t": "unsigned int",
|
||||
"uint32_t": "unsigned int",
|
||||
"int16_t": "int",
|
||||
"int32_t": "int",
|
||||
"uint": "unsigned int",
|
||||
"static ": "",
|
||||
"extern ": "",
|
||||
"__restrict": "",
|
||||
"long __undefined;": "",
|
||||
"return 0;": "return crc32_context % 128;",
|
||||
r"__attribute__ \(\(.*\)\)": "",
|
||||
"_Float128": "long double",
|
||||
"union": "struct",
|
||||
r"enum\s*\{[^\}]*\};": "",
|
||||
"const char \*const sys_errlist\[\];": "",
|
||||
r"[^\n]*printf[^;]*;": "",
|
||||
r"[^\n]*scanf[^;]*;": "",
|
||||
}
|
||||
CSMITH_DIR = "csmith-2.3.0"
|
||||
|
||||
def install_csmith(tests_dir, bin_file):
|
||||
def install_csmith(tests_dir):
|
||||
global CSMITH_DIR
|
||||
csmith_root_dir = os.path.join(tests_dir, CSMITH_DIR)
|
||||
if not os.path.exists(bin_file):
|
||||
subprocess.Popen(["curl", "https://embed.cs.utah.edu/csmith/" + CSMITH_DIR + ".tar.gz", "-o", CSMITH_DIR + ".tar.gz"], cwd=tests_dir).communicate()
|
||||
subprocess.Popen(["tar", "xzvf", CSMITH_DIR + ".tar.gz"], cwd=tests_dir).communicate()
|
||||
subprocess.Popen("cmake .; make -j", shell=True, cwd=csmith_root_dir).communicate()
|
||||
else:
|
||||
print("Using the existing csmith...")
|
||||
|
||||
def generate(tests_dir, bin_file, runtime, file_name):
|
||||
usr_bin_path = "/usr/bin/csmith"
|
||||
usr_inc_path = "/usr/include/csmith"
|
||||
if os.path.exists(usr_bin_path):
|
||||
assert os.path.exists(usr_inc_path)
|
||||
return usr_bin_path, usr_inc_path
|
||||
|
||||
bin_path = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "src/csmith"))
|
||||
inc_path = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "runtime"))
|
||||
if not os.path.exists(bin_path):
|
||||
csmith_filename = "{}.tar.gz".format(CSMITH_DIR)
|
||||
try:
|
||||
args = ["curl", "https://embed.cs.utah.edu/csmith/{}".format(csmith_filename), "-o", csmith_filename]
|
||||
proc = subprocess.Popen(args, cwd=tests_dir)
|
||||
proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise Exception("Failed to download Csmith (exit code: {}): `{}`".format(proc.returncode, " ".join(args)))
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
try:
|
||||
args = ["tar", "xzvf", csmith_filename]
|
||||
proc = subprocess.Popen(args, cwd=tests_dir)
|
||||
proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise Exception("Failed to extract Csmith (exit code: {}): `{}`".format(proc.returncode, " ".join(args)))
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
csmith_root_dir = os.path.join(tests_dir, CSMITH_DIR)
|
||||
try:
|
||||
proc = subprocess.Popen("cmake . && make -j", shell=True, cwd=csmith_root_dir)
|
||||
proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise Exception("Failed to build Csmith (exit code: {})".format(proc.returncode))
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
return bin_path, inc_path
|
||||
|
||||
def generate(tests_dir, bin_path):
|
||||
"""Feeding options to built Csmith to randomly generate test case.
|
||||
|
||||
For generality, I disabled most of the features that are enabled by default.
|
||||
@@ -46,54 +87,103 @@ def generate(tests_dir, bin_file, runtime, file_name):
|
||||
"""
|
||||
global CSMITH_DIR
|
||||
options = [
|
||||
"--no-argc", "--no-arrays", "--no-checksum",
|
||||
"--no-argc", "--no-arrays",
|
||||
"--no-jumps", "--no-longlong", "--no-int8",
|
||||
"--no-uint8", "--no-safe-math", "--no-pointers",
|
||||
"--no-structs", "--no-unions", "--no-builtins"
|
||||
"--no-structs", "--no-unions", "--no-builtins",
|
||||
]
|
||||
args = [bin_file] + options
|
||||
dst_path = os.path.join(runtime, file_name)
|
||||
args = [bin_path] + options
|
||||
|
||||
with open(dst_path, 'w') as f_dst:
|
||||
subprocess.Popen(args, cwd=tests_dir, stdout=f_dst).wait()
|
||||
f_dst.flush()
|
||||
try:
|
||||
proc = subprocess.Popen(args, cwd=tests_dir, stdout=subprocess.PIPE)
|
||||
(src, err) = proc.communicate()
|
||||
return src.decode()
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
return dst_path
|
||||
|
||||
def preprocess(src_path, file_name):
|
||||
"""Preprocessing test case to fit in kecc parser specification.
|
||||
|
||||
It resolves an issue that arbitrarily included header file to hinder parsing.
|
||||
def polish(src, inc_path):
|
||||
"""Polishing test case to fit in kecc parser specification.
|
||||
"""
|
||||
global REPLACE_DICT, CSMITH_DIR
|
||||
with open(src_path, 'r') as src:
|
||||
src = str(src.read())
|
||||
|
||||
for _from, _to in REPLACE_DICT.items():
|
||||
src = src.replace(_from, _to)
|
||||
try:
|
||||
args = ["gcc",
|
||||
"-I", inc_path,
|
||||
"-E",
|
||||
"-",
|
||||
]
|
||||
proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
(src_preprocessed, err) = proc.communicate(src.encode())
|
||||
src_preprocessed = src_preprocessed.decode()
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
with open(os.path.join(os.path.dirname(src_path), file_name), 'w') as dst:
|
||||
dst.write(str(src))
|
||||
src_replaced = src_preprocessed
|
||||
for _from, _to in REPLACE_DICT.items():
|
||||
src_replaced = re.sub(_from, _to, src_replaced)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Fuzzing KECC.')
|
||||
parser.add_argument('-n', '--num', type=int, help='The number of tests')
|
||||
parser.add_argument('-p', '--print', action='store_true', help='Fuzzing C AST printer')
|
||||
args = parser.parse_args()
|
||||
return src_replaced
|
||||
|
||||
if args.print:
|
||||
cargo_arg = "-p"
|
||||
else:
|
||||
raise "Specify fuzzing argument"
|
||||
def make_reduce_criteria(tests_dir, fuzz_arg):
|
||||
"""Make executable reduce_criteria.sh
|
||||
"""
|
||||
# Make shell script i.e. dependent to KECC path
|
||||
arg_dict = {
|
||||
"$PROJECT_DIR": str(Path(tests_dir).parent),
|
||||
"$FUZZ_ARG": fuzz_arg,
|
||||
}
|
||||
with open(os.path.join(tests_dir, "reduce-criteria-template.sh"), "r") as t:
|
||||
temp = t.read()
|
||||
for _from, _to in arg_dict.items():
|
||||
temp = temp.replace(_from, _to)
|
||||
with open(os.path.join(tests_dir, "reduce-criteria.sh"), "w") as f:
|
||||
f.write(temp)
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
csmith_bin = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "src/csmith"))
|
||||
csmith_runtime = os.path.abspath(os.path.join(tests_dir, CSMITH_DIR, "runtime/"))
|
||||
install_csmith(tests_dir, csmith_bin)
|
||||
# chmod the script executable
|
||||
try:
|
||||
args = ["chmod", "u+x", "reduce-criteria.sh"]
|
||||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir)
|
||||
proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise Exception("`{}` failed with exit code {}.".format(" ".join(args), proc.returncode))
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
# Run cargo test infinitely
|
||||
raw_test_file = "raw_test.c"
|
||||
test_file = "test.c"
|
||||
def creduce(tests_dir, fuzz_arg):
|
||||
"""Reduce `tests/test_polished.c` to `tests/test_reduced.c`
|
||||
|
||||
First, we copy test_polished.c to test_reduced.c.
|
||||
Then, when Creduce reduces test_reduced.c, it overwrites partially reduced program to itself.
|
||||
Original file is moved to test_reduced.c.orig which is then identical to test_polished.c.
|
||||
"""
|
||||
make_reduce_criteria(tests_dir, fuzz_arg)
|
||||
|
||||
try:
|
||||
args = ["cp", "test_polished.c", "test_reduced.c"]
|
||||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir)
|
||||
proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise Exception("`{}` failed with exit code {}.".format(" ".join(args), proc.returncode))
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
try:
|
||||
args = ["creduce", "./reduce-criteria.sh", "test_reduced.c"]
|
||||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir)
|
||||
(out, err) = proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
print(out.decode())
|
||||
raise Exception("Reducing test_reduced.c by `{}` failed with exit code {}.".format(" ".join(args), proc.returncode))
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
def fuzz(tests_dir, fuzz_arg, num_iter):
|
||||
csmith_bin, csmith_inc = install_csmith(tests_dir)
|
||||
try:
|
||||
print("Building KECC..")
|
||||
try:
|
||||
@@ -103,27 +193,55 @@ if __name__ == "__main__":
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
if args.num is None:
|
||||
if num_iter is None:
|
||||
print("Fuzzing with infinitely many test cases. Please press [ctrl+C] to break.")
|
||||
iterator = itertools.count(0)
|
||||
else:
|
||||
print("Fuzzing with {} test cases.".format(args.num))
|
||||
iterator = range(args.num)
|
||||
print("Fuzzing with {} test cases.".format(num_iter))
|
||||
iterator = range(num_iter)
|
||||
|
||||
for i in iterator:
|
||||
print("Test case #{}".format(i))
|
||||
preprocess(generate(tests_dir, csmith_bin, csmith_runtime, raw_test_file), test_file)
|
||||
args = ["cargo", "run", "--release", "--bin", "fuzz", "--", cargo_arg, os.path.join(csmith_runtime, test_file)]
|
||||
src = generate(tests_dir, csmith_bin)
|
||||
with open(os.path.join(tests_dir, "test.c"), 'w') as dst:
|
||||
dst.write(src)
|
||||
|
||||
src_polished = polish(src, csmith_inc)
|
||||
with open(os.path.join(tests_dir, "test_polished.c"), 'w') as dst:
|
||||
dst.write(src_polished)
|
||||
|
||||
try:
|
||||
args = ["cargo", "run", "--release", "--bin", "fuzz", "--", fuzz_arg, os.path.join(tests_dir, "test_polished.c")]
|
||||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tests_dir)
|
||||
(out, err) = proc.communicate(timeout=10)
|
||||
proc.communicate(timeout=10)
|
||||
if proc.returncode != 0:
|
||||
raise Exception("Test `{}` failed with exit code {}.".format(" ".join(args), proc.returncode))
|
||||
except subprocess.TimeoutExpired as e:
|
||||
proc.kill()
|
||||
raise e
|
||||
|
||||
except KeyboardInterrupt:
|
||||
proc.terminate()
|
||||
print("\n[Ctrl+C] interrupted")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Fuzzing KECC.')
|
||||
parser.add_argument('-n', '--num', type=int, help='The number of tests')
|
||||
parser.add_argument('-p', '--print', action='store_true', help='Fuzzing C AST printer')
|
||||
parser.add_argument('-i', '--irgen', action='store_true', help='Fuzzing irgen')
|
||||
parser.add_argument('-r', '--reduce', action='store_true', help="Reducing input file")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.print and args.irgen:
|
||||
raise Exception("Choose an option used for fuzzing: '--print' or '--irgen', NOT both")
|
||||
if args.print:
|
||||
fuzz_arg = "-p"
|
||||
elif args.irgen:
|
||||
fuzz_arg = "-i"
|
||||
else:
|
||||
raise Exception("Specify fuzzing argument")
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
if args.reduce:
|
||||
creduce(tests_dir, fuzz_arg)
|
||||
else:
|
||||
fuzz(tests_dir, fuzz_arg, args.num)
|
||||
|
||||
3
tests/reduce-criteria-template.sh
Normal file
3
tests/reduce-criteria-template.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
! cargo run --manifest-path $PROJECT_DIR/Cargo.toml --release --bin fuzz -- $FUZZ_ARG test_reduced.c
|
||||
@@ -1,47 +1,43 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
use lang_c::ast::*;
|
||||
|
||||
use kecc::run_ir::*;
|
||||
use kecc::*;
|
||||
|
||||
fn test_dir<F>(path: &Path, f: F)
|
||||
where
|
||||
F: Fn(&TranslationUnit),
|
||||
F: Fn(&TranslationUnit, &Path),
|
||||
{
|
||||
let mut parse = Parse::default();
|
||||
let dir = path.read_dir().expect("read_dir call failed");
|
||||
for entry in dir {
|
||||
let entry = ok_or!(entry, continue);
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
|
||||
if !(path.is_file() && path.extension() == Some(&OsStr::new("c"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("[testing {:?}]", path);
|
||||
let test_unit = parse.translate(&path.as_path()).expect(
|
||||
&format!("parse failed {:?}", path.into_os_string().to_str().unwrap()).to_owned(),
|
||||
&format!(
|
||||
"parse failed {:?}",
|
||||
path.clone().into_os_string().to_str().unwrap()
|
||||
)
|
||||
.to_owned(),
|
||||
);
|
||||
f(&test_unit);
|
||||
f(&test_unit, &path);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_write_c() {
|
||||
test_dir(Path::new("examples/"), write_c_test);
|
||||
test_dir(Path::new("examples/hw1"), write_c_test);
|
||||
test_dir(Path::new("examples/"), test_write_c);
|
||||
test_dir(Path::new("examples/hw1"), test_write_c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_irgen() {
|
||||
test_dir(Path::new("examples/"), |test_unit| {
|
||||
let ir = Irgen::default()
|
||||
.translate(test_unit)
|
||||
.expect("failed to generate ir");
|
||||
|
||||
// TODO: insert randomly generated command line arguments
|
||||
let args = Vec::new();
|
||||
|
||||
assert_eq!(run_ir(&ir, args), Ok(Value::Int(1)));
|
||||
});
|
||||
test_dir(Path::new("examples/"), test_irgen);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user