8000 Added Kaleidoscope example by 71 · Pull Request #19 · TheDan64/inkwell · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Added Kaleidoscope example #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Oct 4, 2017
Merged

Added Kaleidoscope example #19

merged 19 commits into from
Oct 4, 2017

Conversation

71
Copy link
Contributor
@71 71 commented Oct 2, 2017

Added the Kaleidoscope example to the project. Additionally, write_bitcode_to_file now compiles on Windows (although it will always fail and return false).

Note: Yes, the first three commits have the wrong author, but after an hour of trying to fix this, I just give up on the fame.

@71
Copy link
Contributor Author
71 commented Oct 2, 2017

Oh, by the way, this should lead to the update of #2.

@codecov
Copy link
codecov bot commented Oct 2, 2017

Codecov Report

Merging #19 into master will not change coverage.
The diff coverage is 0%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master      #19   +/-   ##
=======================================
  Coverage   46.06%   46.06%           
=======================================
  Files          35       35           
  Lines        3645     3645           
=======================================
  Hits         1679     1679           
  Misses       1966     1966
Impacted Files Coverage Δ
src/execution_engine.rs 35.95% <ø> (ø) ⬆️
src/module.rs 49.26% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cdc1560...92ae1da. Read the comment docs.

@TheDan64 TheDan64 self-requested a review October 3, 2017 01:06
@71
Copy link
Contributor Author
71 commented Oct 3, 2017

Want me to add tests to Kaleidoscope? In any case, I'll add some improvements, and after this I'm planning to work on the documentation. I also have some remarks on some structures and enums

Copy link
Owner
@TheDan64 TheDan64 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot here, so I'm going to do another pass or two in the next couple days. But I did my initial review and it looks great overall.

I'd prefer to avoid transmute like the plague, so please help me understand why they're really needed so we can think about how to replace the ones that aren't.

Also, kaleidoscope.rs is huge! Thank you for all of the time that you've put into this. I really appreciate it. I think we should split it into at least three or four files:

examples/kaleidoscope/lexer.rs
examples/kaleidoscope/parser.rs
examples/kaleidoscope/llvm.rs
and maybe a main/drive type of file, where we can add commandline argument parsing stuff, if needed?

};
}

pub extern fn putchard(x: f64) -> f64 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these extern functions for? If you need putchar, you should be able to call the llvm putchar function. It's also unclear why you truncate a 64bit float down to an 8 bit int and then back up to a char?

fpm: pass_manager,
module: module,
function: function,
fn_value: unsafe { std::mem::transmute(0usize) },
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transmute should be avoided unless absolutely necessary. It looks to me like this could be replaced with a None of type Option<FunctionValue>?

if is_anonymous {
let ee = module.create_jit_execution_engine(0).unwrap();

// 2017-02-10 <6A> I still can't add my own functions with either add_global_mapping or add_symbol.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why add_global_mapping and add_symbol are needed? Also, I don't believe LLVMAddSymbol was introduced until 3.7, and I would like this to eventually work in 3.6 as well.


/// Represents a primitive syntax token.
#[derive(Debug, Clone)]
pub enum Token {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a personal preference, but would you mind keeping one enum variant per line and alphabetizing them? I find it's easier to visually find variants when looking at it that way.


// Skip whitespaces
loop {
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hard to tell from just looking at the code, do the two scopes need to be separate? If so, maybe add a comment explaining why.

let var_val = self.compile_expr(&right)?;
let var = self.variables.get(var_name.as_str()).ok_or("")?;

Ok(unsafe { std::mem::transmute(self.builder.build_store(&var, &var_val)) })
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this transmuting to? IIRC store doesn't return useful data but the instruction. Maybe you want to return the actual value that was stored?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method returns a FloatValue, but build_store returns a PointerValue. I'm casting the latter to the former here, since the structure is actually a pointer to a float.

(&else_val, &else_bb)
]);

Ok(unsafe { std::mem::transmute(phi) })
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to do phi.as_basic_value().into_float_value() or similar in the latest version of Inkwell

let curr_var = self.builder.build_load(&start_alloca, var_name);
let next_var = self.builder.build_float_add(&curr_var.as_float_value(), &step, "nextvar");

self.builder.build_store(&unsafe { std::mem::transmute(next_var) }, &start_alloca);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is a float being used as a pointer? Did you mean build_store(&start_alloca, &next_var)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks!

@71
Copy link
Contributor Author
71 commented Oct 3, 2017

Yeah, in theory transmute should never be used, but I let it up the time to fix some issues I found with Inkwell. Furthermore everything is in a single file, which is, to me, a plus. It is a little less overwhelming to see a single file with 1300 LoC than 5 smaller files. But this is your project, and thus your choice. I'll reply to your other comments as soon as I have the time.

@71
Copy link
Contributor Author
71 commented Oct 3, 2017

My last commit should address all your concerns. The only transmute call left is necessary in order to invoke the compiled function.

@TheDan64
Copy link
Owner
TheDan64 commented Oct 3, 2017

I got to try it out and I like it a lot!

Some thoughts:

  • > arrow has a space to the left of it that seems out of place
  • Hitting enter at the prompt panics with index out of bounds, maybe this should just be a no-op that prints a newline and the prompt again?
  • LLVM IR seems to always output, and 2x when the cli arg is passed in
  • Even if you don't want to split up the file, it should still go in its own directory, along with a README.md describing the basic syntax (stuff like variable declarations, functions, etc)

@@ -50,6 +50,12 @@ impl ExecutionEngine {
}
}

pub fn add_global_mapping(&mut self, global: &AnyValue, addr: *const ()) {
Copy link
Owner
@TheDan64 TheDan64 Oct 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might make sense to remove this for now since it's not used and is likely to change anyway. Long term it may end up looking something like this:

struct MemoryAddress(usize);

pub fn add_global_mapping(&(mut?) self, global: &GlobalValue, addr: &MemoryAddress) {}

For example, the execution engine outputs an address which can easily be moved or copied out of the EE's scope, so I think that will end up returning a non-Copy &MemoryAddress so that the underlying value is kept in scope but is still transmutable to a function. Maybe the user will be able to create their own (btw, you can safely cast any pointer to a usize like so: let ptr_val = &a as *const _ as usize; or similar)

What do you think?

// REVIEW: as_raw_fd docs suggest it only works in *nix
// Also, should_close should maybe be hardcoded to true?
unsafe {
LLVMWriteBitcodeToFD(self.module, file.as_raw_fd(), should_close as i32, unbuffered as i32) == 0
}
}

#[cfg(windows)]
#[allow(unused_variables)]
pub fn write_bitcode_to_file(&self, file: &File, should_close: bool, unbuffered: bool) -> bool {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet! Thanks! I hope we'll be able to get this working on windows eventually, and if not we'll probably just remove the method completely from windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting the file's raw handle might work, but I haven't tried it out.

loop {
let ch = match chars.peek() {
Some(ch) => *ch,
None => { return Ok(Token::EOF); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, I believe you can just do None => return Ok(Token::EOF),

loop {
let ch = match chars.peek() {
Some(ch) => *ch,
None => { return Ok(Token::EOF); }
Copy link
Owner
@TheDan64 TheDan64 Oct 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon


let op = match self.curr() {
Op(ch) => ch,
_ => { return Err("Expected operator in custom operator declaration."); }
Copy link
Owner
@TheDan64 TheDan64 Oct 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon


let op = match self.curr() {
Op(ch) => ch,
_ => { return Err("Expected operator in custom operator declaration."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

(name, true, 0)
},

_ => { return Err("Expected identifier in prototype declaration.") }
Copy link
Owner
@TheDan64 TheDan64 Oct 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets


match self.curr() {
LParen => (),
_ => { return Err("Expected '(' character in prototype declaration.") }
Copy link
Owner
@TheDan64 TheDan64 Oct 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets

loop {
match self.curr() {
Ident(name) => args.push(name),
_ => { return Err("Expected identifier in parameter declaration.") }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets

Comma => {
self.advance();
},
_ => { return Err("Expected ',' or ')' character in prototype declaration.") }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets

fn parse_paren_expr(&mut self) -> Result<Expr, &'static str> {
match self.current()? {
LParen => (),
_ => { return Err("Expected '(' character at start of parenthesized expression.") }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets


match self.current()? {
RParen => (),
_ => { return Err("Expected ')' character at end of parenthesized expression.") }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets

fn parse_id_expr(&mut self) -> Result<Expr, &'static str> {
let id = match self.curr() {
Ident(id) => id,
_ => { return Err("Expected identifier."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

match self.current()? {
Comma => (),
RParen => break,
_ => { return Err("Expected ',' character in function call."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

self.advance()?;
ch
},
_ => { return self.parse_primary(); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon


let op = match self.curr() {
Op(op) => op,
_ => { return Err("Invalid operator."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

// eat 'then' token
match self.current() {
Ok(Then) => { self.advance()? },
_ => { return Err("Expected 'then' keyword."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

// eat 'else' token
match self.current() {
Ok(Else) => { self.advance()? },
_ => { return Err("Expected 'else' keyword."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon


let name = match self.curr() {
Ident(n) => n,
_ => { return Err("Expected identifier in for loop."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

// eat '=' token
match self.curr() {
Op('=') => { self.advance()?; },
_ => { return Err("Expected '=' character in for loop."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

// eat ',' token
match self.current()? {
Comma => { self.advance()?; },
_ => { return Err("Expected ',' character in for loop."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

// eat 'in' token
match self.current()? {
In => { self.advance()?; },
_ => { return Err("Expected 'in' keyword in for loop."); }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for brackets or semicolon

@TheDan64
Copy link
Owner
TheDan64 commented Oct 3, 2017

Some stylistic comments, but this PR is starting to look pretty good. Did you try running clippy on it by any chance?

@71
Copy link
Contributor Author
71 commented Oct 3, 2017

Nope, I'll get to it right away. Also thanks for your stylistic comments, but it wasn't worth the trouble to comment every single mistake... And I just realized I let up something I had used for testing purposes... Oops. Oh, and the space on the left of the > prompt is used to align with the returned =>.

@TheDan64
Copy link
Owner
TheDan64 commented Oct 3, 2017

No trouble at all. I'll leave it up to you as to whether you want the space or not. Maybe consider something like Python's approach:

> 1 + 1
2

@71
Copy link
Contributor Author
71 commented Oct 3, 2017

I instead went with a different approach and prefixed all input with ?>, all debug messages with ->, all errors with !> and all output with =>. I also think everything should be good to go. Some examples have been added, as well as a README.

@TheDan64
Copy link
Owner
TheDan64 commented Oct 4, 2017

Cool!

@TheDan64 TheDan64 merged commit fd5199a into TheDan64:master Oct 4, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0