Skip to content
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

Fix macro interpreter Number type of arithmetic expressions #5972

Merged
merged 4 commits into from
Jun 7, 2018

Conversation

straight-shoota
Copy link
Member

NumberLiteral constructor receiving the value as a Number now uses the number type to determine it's kind. The few cases where a specific other type is required are changed to use the constructor receiving String plus explicit kind.

Fixes #5971

Copy link
Member

@asterite asterite left a comment

Choose a reason for hiding this comment

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

Awesome 👍

@asterite
Copy link
Member

Though it seems CI failed...

@straight-shoota
Copy link
Member Author

That's odd... need to investigate.

@@ -228,6 +228,24 @@ module Crystal

def_equals value.to_f64, kind
def_hash value, kind

def self.kind_from_number(number : Number)
case number.class
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't this just need to get rid of the .class?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, doing number.class causes it to raise:

# with case number.class
kind_from_number 1_u64 # => Unsupported Number type for NumberLiteral: UInt64 (Exception)

# with case number
kind_from_number(1_u64) # => :uint64

@@ -214,8 +214,8 @@ module Crystal
def initialize(@value : String, @kind = :i32)
end

def initialize(value : Number, @kind = :i32)
@value = value.to_s
def self.new(value : Number)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not continue to accept .new(Number, kind: Symbol), but have .new(Number) take the type of the number. This way you don't need the to_s additions above (they all have explicit kind anyway).

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think this would be that useful. It's a simple API now: If the constructor receives a Number, it deduces kind accordingly. Otherwise you need to specify value (as string) and kind explicitly.

@@ -228,6 +228,24 @@ module Crystal

def_equals value.to_f64, kind
def_hash value, kind

def self.kind_from_number(number : Number)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the only usage of kind_from_number? or there are plans to reuse it somewhere else?

Asking this because either make it protected/private or add :nodoc:.

Thank you.

Copy link
Member Author

Choose a reason for hiding this comment

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

Atm it is the only usage, I don't know if it there is a different use case.

Compiler types and methods are not really documented anyway, so there is not much gain from adding :nodoc:.

I don't think there are any particular guidelines about this for the compiler.

@straight-shoota
Copy link
Member Author

Seems like this change struck something else... might be a hidden bug? I don't know anything about Crystal's codegen.

@asterite
Copy link
Member

asterite commented Apr 21, 2018

You'll have to dump the LL and see what's going on. This PR probably broke something. When that breaks the compiler it's nightmare debugging time. Good luck :-)

@straight-shoota
Copy link
Member Author

I had to disable LLVM module validation in order to dump the IR, otherwise it would fail during validation and don't output anything.

Then I used a modified compiler including this patch to compare it's LLVM IR of an empty crystal file to the compiler v0.24.2. It looks like this patch added explicit type information to int values which were previously omitted. For example, with this patch, the IR for String::Builder#real_bytesize has the value of String::HEADER_SIZE explicitly typed as i64 which doesn't match the type the other summand @bytesize:

; v0.24.2:
%2 = add i32 %1, 12, !dbg !413
; this patch:
%2 = add i32 %1, i64 12, !dbg !1624

@straight-shoota
Copy link
Member Author

It seems like the error was caused by NumberLiterals created for SizeOf depended on the default value kind = :i32. Because Program#size_of returns Int32 | Int64 there would be a mismatch between number.type (Int64) and kind (:i32).

Setting kind to :i32 in those methods solves the immediate failure, but I'm not sure if this is the right fix. Could probably also set type to Int64 and sizeof would return Int64.

But in general, shouldn't the codegen step identify such mismatching types in operations and convert them?

@@ -699,7 +699,7 @@ module Crystal
end

if value
NumberLiteral.new(value)
NumberLiteral.new(value.to_s, :i32)
Copy link
Contributor

Choose a reason for hiding this comment

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

a few line above the value is converted to an Int64 (@value.to_i64?), it feels weird to 'cast' it to :i32 here, is it intentional ?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is intentional as far as this prevents codegen to fail. 🤷‍♂️ It's only a workaround, though.
In fact, this isn't even a change: it implicitly was :i32 before, now it's just explicit.

But yes, it should probably be adjusted, however we need to fix the codegen bug first.

@@ -2469,7 +2469,7 @@ module Crystal
# (useful for sizeof inside as a generic type argument, but also
# to make it easier for LLVM to optimize things)
if !node.exp.is_a?(TypeOf)
expanded = NumberLiteral.new(@program.size_of(type.sizeof_type))
expanded = NumberLiteral.new(@program.size_of(type.sizeof_type).to_s, :i32)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add a comment to explain why it's important to make them :i32 here?

(and ditto below)

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't even really know, why exactly this is necessary. 😅 The type restriction can be removed when codegen bug is fixed.

Copy link
Member

Choose a reason for hiding this comment

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

There's no codegen bug. It was working previously.

Copy link
Member Author

Choose a reason for hiding this comment

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

If there's no bug, why are the second and third commit necessary? Maybe I'm missing something... but it feels to me that these types should automatically be adjusted.

@straight-shoota straight-shoota changed the title Fix macro interpreter Number type of arithmetic expressions (WIP) Fix macro interpreter Number type of arithmetic expressions Apr 24, 2018
@straight-shoota
Copy link
Member Author

straight-shoota commented Apr 24, 2018

The reason for this seems to be that at the codegen stage there is no explicit enforcement of operand types for numerical operations such as mul. The LLVM IR builder doesn't complain either and only at the verify stage mismatching types are recognized as an error.

For normal code this is not an issue because the Crystal types already ensure operand types are appropriately coerced. This matters only if the first operand (target) is a primitive.

Example:

{{ -1_i64 }} * 12_u8 # => -12_i64
12_u8 * {{ -1_i64 }} # => 244_u8

This PR doesn't even fix the example (yet), it only treated the symptoms in some places.

The condition for this bug is that the first operand is a primitive and the seconds operand (call argument) is a NumberType of the same class of number types (ints, floats) but with a different size.

I'll try to fix it by adding an additional type check (and conversion, if necessary) in codegen_binary_op for number types.

Actually, there is already codegen_binary_extend_int yet it seems not to work properly in the described condition...

@asterite
Copy link
Member

sizeof's type is Int32 because you won't have a class or a struct whose size is bigger than 2GB...

@straight-shoota
Copy link
Member Author

@asterite Yes, such large sizes don't make much sense. But why does Program#size_of return UInt64 | Int32 then? LLVMSizeOfTypeInBits probably has return type unsigned long long for a reason?

But regardless of the kind used in the NumberLiteral, the bin op codegen should simply adjust it accordingly to produce a valid operation between matching types.

@RX14
Copy link
Contributor

RX14 commented Apr 26, 2018

yeah LLVMSizeOfTypeInBits is UInt64

@asterite
Copy link
Member

@straight-shoota Oh, I think I understand now what you think is a compiler bug.

Note this line:

node.type = @program.int32

Here the compiler is basically "inferring" (or hardcoding) the type of sizeof to be Int32, and expect it to be that type in the codegen. If you go and change the underlying type of the value that sizeof expands to, for example to Int64, then of course it will blow up, because the compiler won't add extra checks or casts for things it expects them to be a certain way.

Does that make sense?

@straight-shoota
Copy link
Member Author

Ah, okay that type is then passed to codegen_binary_op so it's Int32 even if the actual value
in the expanded NumberLiteral is Int64. But no conversion will be applied.

@straight-shoota
Copy link
Member Author

So, then it's probably good to merge?

@straight-shoota straight-shoota changed the title (WIP) Fix macro interpreter Number type of arithmetic expressions Fix macro interpreter Number type of arithmetic expressions Apr 28, 2018
@asterite
Copy link
Member

Yes, but travis doesn't agree

@straight-shoota
Copy link
Member Author

Just ignore travis 😁 It seems to be an unrelated memory issue.

Or trigger a re-run.

@Sija
Copy link
Contributor

Sija commented Apr 28, 2018

@straight-shoota nope, now it's related to recently merged #5954 (see https://travis-ci.org/crystal-lang/crystal/jobs/369805514#L543-L548).

@straight-shoota
Copy link
Member Author

Oh, that's different now.

The question is: Why does this only raise on travis 64-bit? Or rather, why does this raise at all? This PR hasn't even been rebased to include #5954!

@bew
Copy link
Contributor

bew commented Jun 3, 2018

I guess it's because the CI script tries to run the master's spec at some point (.. huh no I don't know)

Can you rebase?

@straight-shoota
Copy link
Member Author

Rebased

@sdogruyol
Copy link
Member

@straight-shoota can you fix the conflicts?

@sdogruyol sdogruyol merged commit 562a793 into crystal-lang:master Jun 7, 2018
@sdogruyol sdogruyol added this to the 0.25.0 milestone Jun 7, 2018
@straight-shoota straight-shoota deleted the jm/fix/5971 branch June 7, 2018 10:45
@RX14 RX14 added the kind:bug A bug in the code. Does not apply to documentation, specs, etc. label Jun 7, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants