Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 60bf3cdad591
Choose a base ref
...
head repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: c7921397a6c5
Choose a head ref
  • 6 commits
  • 11 files changed
  • 1 contributor

Commits on Dec 16, 2016

  1. Added IO#read_fully?

    Ary Borenszweig committed Dec 16, 2016
    Copy the full SHA
    fab210f View commit details
  2. FileUtils: fixed bug in cmp method

    Ary Borenszweig committed Dec 16, 2016
    Copy the full SHA
    3cd37d8 View commit details
  3. Compiler: use consistent type sort across compilations when processin…

    …g type declarations
    Ary Borenszweig committed Dec 16, 2016
    Copy the full SHA
    f92334e View commit details
  4. LLVM: bind MemoryBuffer

    Ary Borenszweig committed Dec 16, 2016
    4
    Copy the full SHA
    fb86c74 View commit details
  5. File.write: accept Bytes and IO as arguments

    Ary Borenszweig committed Dec 16, 2016
    Copy the full SHA
    0ef2c6f View commit details
  6. Compiler: optimize codegen performance

    Ary Borenszweig committed Dec 16, 2016
    20
    Copy the full SHA
    c792139 View commit details
16 changes: 16 additions & 0 deletions spec/std/file_spec.cr
Original file line number Diff line number Diff line change
@@ -493,6 +493,22 @@ describe "File" do
File.delete(filename)
end

it "writes bytes" do
filename = "#{__DIR__}/data/temp_write.txt"
File.write(filename, "hello".to_slice)
File.read(filename).should eq("hello")
File.delete(filename)
end

it "writes io" do
filename = "#{__DIR__}/data/temp_write.txt"
File.open("#{__DIR__}/data/test_file.txt") do |file|
File.write(filename, file)
end
File.read(filename).should eq(File.read("#{__DIR__}/data/test_file.txt"))
File.delete(filename)
end

it "raises if trying to write to a file not opened for writing" do
filename = "#{__DIR__}/data/temp_write.txt"
File.write(filename, "hello")
47 changes: 47 additions & 0 deletions spec/std/file_utils_spec.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
require "spec"
require "file_utils"

private class OneByOneIO
include IO

@bytes : Bytes

def initialize(string)
@bytes = string.to_slice
@pos = 0
end

def read(slice : Slice(UInt8))
return 0 if slice.empty?
return 0 if @pos >= @bytes.size

slice[0] = @bytes[@pos]
@pos += 1
1
end

def write(slice : Slice(UInt8)) : Nil
end
end

describe "FileUtils" do
describe "cd" do
it "should work" do
@@ -48,6 +71,30 @@ describe "FileUtils" do
File.join(__DIR__, "data/test_file.ini")
).should be_false
end

it "compares two ios, one way (true)" do
io1 = OneByOneIO.new("hello")
io2 = IO::Memory.new("hello")
FileUtils.cmp(io1, io2).should be_true
end

it "compares two ios, second way (true)" do
io1 = OneByOneIO.new("hello")
io2 = IO::Memory.new("hello")
FileUtils.cmp(io2, io1).should be_true
end

it "compares two ios, one way (false)" do
io1 = OneByOneIO.new("hello")
io2 = IO::Memory.new("hella")
FileUtils.cmp(io1, io2).should be_false
end

it "compares two ios, second way (false)" do
io1 = OneByOneIO.new("hello")
io2 = IO::Memory.new("hella")
FileUtils.cmp(io2, io1).should be_false
end
end

describe "cp" do
11 changes: 10 additions & 1 deletion spec/std/io/io_spec.cr
Original file line number Diff line number Diff line change
@@ -344,13 +344,22 @@ describe IO do
it "does read_fully" do
str = SimpleIOMemory.new("hello")
slice = Slice(UInt8).new(4)
str.read_fully(slice)
str.read_fully(slice).should eq(4)
String.new(slice).should eq("hell")

expect_raises(IO::EOFError) do
str.read_fully(slice)
end
end

it "does read_fully?" do
str = SimpleIOMemory.new("hello")
slice = Slice(UInt8).new(4)
str.read_fully?(slice).should eq(4)
String.new(slice).should eq("hell")

str.read_fully?(slice).should be_nil
end
end

describe "write operations" do
85 changes: 33 additions & 52 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
@@ -248,29 +248,20 @@ module Crystal

private def codegen(program, units : Array(CompilationUnit), lib_flags, output_filename, output_dir)
object_names = units.map &.object_filename
multithreaded = LLVM.start_multithreaded

# First write bitcodes: it breaks if we paralellize it
unless multithreaded
Crystal.timing("Codegen (crystal)", @stats) do
units.each &.write_bitcode
end
end

msg = multithreaded ? "Codegen (bc+obj)" : "Codegen (obj)"
target_triple = target_machine.triple

Crystal.timing(msg, @stats) do
Crystal.timing("Codegen (bc+obj)", @stats) do
if units.size == 1
first_unit = units.first

codegen_single_unit(program, first_unit, target_triple, multithreaded)
codegen_single_unit(program, first_unit, target_triple)

if emit = @emit
first_unit.emit(emit, emit_base_filename || output_filename)
end
else
codegen_many_units(program, units, target_triple, multithreaded)
codegen_many_units(program, units, target_triple)
end
end

@@ -288,37 +279,28 @@ module Crystal
end
end

private def codegen_many_units(program, units, target_triple, multithreaded)
private def codegen_many_units(program, units, target_triple)
jobs_count = 0
wait_channel = Channel(Nil).new(@n_threads)

while unit = units.pop?
fork_and_codegen_single_unit(program, unit, target_triple, multithreaded, wait_channel)
units.each_slice(Math.max(units.size / @n_threads, 1)) do |slice|
jobs_count += 1

if jobs_count >= @n_threads
wait_channel.receive
jobs_count -= 1
spawn do
codegen_process = fork do
slice.each do |unit|
codegen_single_unit(program, unit, target_triple)
end
end
codegen_process.wait
wait_channel.send nil
end
end

while jobs_count > 0
wait_channel.receive
jobs_count -= 1
end
end

private def fork_and_codegen_single_unit(program, unit, target_triple, multithreaded, wait_channel)
spawn do
codegen_process = fork { codegen_single_unit(program, unit, target_triple, multithreaded) }
codegen_process.wait
wait_channel.send nil
end
jobs_count.times { wait_channel.receive }
end

private def codegen_single_unit(program, unit, target_triple, multithreaded)
private def codegen_single_unit(program, unit, target_triple)
unit.llvm_mod.target = target_triple
unit.write_bitcode if multithreaded
unit.compile
end

@@ -420,16 +402,11 @@ module Crystal
end
end

def write_bitcode
llvm_mod.write_bitcode(bc_name_new)
end

def compile
bc_name = self.bc_name
bc_name_new = self.bc_name_new
object_name = self.object_name

must_compile = true
memory_buffer = llvm_mod.write_bitcode

# To compile a file we first generate a `.bc` file and then
# create an object file from it. These `.bc` files are stored
@@ -442,18 +419,26 @@ module Crystal
# the `.o` file will also be the same, so we simply reuse the
# old one. Generating an `.o` file is what takes most time.
if !compiler.emit && !@bc_flags_changed && File.exists?(bc_name) && File.exists?(object_name)
if FileUtils.cmp(bc_name, bc_name_new)
# If the user cancelled a previous compilation it might be that
# the .o file is empty
if File.size(object_name) > 0
File.delete bc_name_new
must_compile = false
end
memory_io = IO::Memory.new(memory_buffer.to_slice)
changed = File.open(bc_name) { |bc_file| !FileUtils.cmp(bc_file, memory_io) }

# If the user cancelled a previous compilation
# it might be that the .o file is empty
if !changed && File.size(object_name) > 0
# We can skip compilation
memory_buffer.dispose
memory_buffer = nil
else
# We need to compile, so we'll write the memory buffer to file
end
end

if must_compile
File.rename(bc_name_new, bc_name)
# If there's a memory buffer, it means we must create a .o from it
if memory_buffer
# Create the .bc file (for next compilations)
File.write(bc_name, memory_buffer.to_slice)
memory_buffer.dispose

compiler.optimize llvm_mod if compiler.release?
compiler.target_machine.emit_obj_to_file llvm_mod, object_name
end
@@ -492,10 +477,6 @@ module Crystal
"#{@output_dir}/#{@name}.bc"
end

def bc_name_new
"#{@output_dir}/#{@name}.new.bc"
end

def ll_name
"#{@output_dir}/#{@name}.ll"
end
17 changes: 4 additions & 13 deletions src/compiler/crystal/semantic/type_declaration_processor.cr
Original file line number Diff line number Diff line change
@@ -677,19 +677,10 @@ struct Crystal::TypeDeclarationProcessor
# We sort types. We put modules first, because if these declare types
# of instance variables we want them declared in including types.
# Then we sort other types by depth, so we declare types first in
# superclass and then in subclasses.
types.sort! do |t1, t2|
if t1.module?
if t2.module?
t1.object_id <=> t2.object_id
else
-1
end
elsif t2.module?
1
else
t1.depth <=> t2.depth
end
# superclass and then in subclasses. Finally, two modules or classes
# with the same depths are sorted by name.
types.sort_by! do |t|
{t.module? ? 0 : 1, t.depth, t.to_s}
end
end

16 changes: 14 additions & 2 deletions src/file.cr
Original file line number Diff line number Diff line change
@@ -469,16 +469,28 @@ class File < IO::FileDescriptor
lines
end

# Write the given content to *filename*.
# Write the given *content* to *filename*.
#
# An existing file will be overwritten, else a file will be created.
#
# ```
# File.write("foo", "bar")
# ```
#
# If the content is a `Slice(UInt8)`, those bytes will be written. If it's
# an `IO`, all bytes from the IO will be written. Otherwise, the string
# representation of *content* will be written (the result of invoking `to_s`
# on *content*)
def self.write(filename, content, perm = DEFAULT_CREATE_MODE, encoding = nil, invalid = nil)
File.open(filename, "w", perm, encoding: encoding, invalid: invalid) do |file|
file.print(content)
case content
when Bytes
file.write(content)
when IO
IO.copy(content, file)
else
file.print(content)
end
end
end

6 changes: 3 additions & 3 deletions src/file_utils.cr
Original file line number Diff line number Diff line change
@@ -45,10 +45,10 @@ module FileUtils
buf2 = uninitialized UInt8[1024]

while true
read1 = stream1.read buf1.to_slice
read2 = stream2.read buf2.to_slice
read1 = stream1.read(buf1.to_slice)
read2 = stream2.read_fully?(buf2.to_slice[0, read1])
return false unless read2

return false if read1 != read2
return false if buf1.to_unsafe.memcmp(buf2.to_unsafe, read1) != 0
return true if read1 == 0
end
23 changes: 19 additions & 4 deletions src/io.cr
Original file line number Diff line number Diff line change
@@ -492,15 +492,30 @@ module IO
# ```
# io = IO::Memory.new "123451234"
# slice = Slice(UInt8).new(5)
# io.read_fully(slice)
# slice # => [49, 50, 51, 52, 53]
# io.read_fully # => EOFError
# io.read_fully(slice) # => 5
# slice # => [49, 50, 51, 52, 53]
# io.read_fully # => EOFError
# ```
def read_fully(slice : Slice(UInt8))
read_fully?(slice) || raise(EOFError.new)
end

# Tries to read exactly `slice.size` bytes from this IO into `slice`.
# Returns `nil` if there aren't `slice.size` bytes of data, otherwise
# returns the number of bytes read.
#
# ```
# io = IO::Memory.new "123451234"
# slice = Slice(UInt8).new(5)
# io.read_fully?(slice) # => 5
# slice # => [49, 50, 51, 52, 53]
# io.read_fully? # => nil
# ```
def read_fully?(slice : Slice(UInt8))
count = slice.size
while slice.size > 0
read_bytes = read slice
raise EOFError.new if read_bytes == 0
return nil if read_bytes == 0
slice += read_bytes
end
count
5 changes: 5 additions & 0 deletions src/llvm/lib_llvm.cr
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@ lib LibLLVM
type PassManagerBuilderRef = Void*
type PassManagerRef = Void*
type PassRegistryRef = Void*
type MemoryBufferRef = Void*

struct JITCompilerOptions
opt_level : UInt32
@@ -294,6 +295,10 @@ lib LibLLVM
fun dispose_pass_manager_builder = LLVMPassManagerBuilderDispose(PassManagerBuilderRef)
fun set_volatile = LLVMSetVolatile(value : ValueRef, volatile : UInt32)
fun set_alignment = LLVMSetAlignment(value : ValueRef, bytes : UInt32)
fun write_bitcode_to_memory_buffer = LLVMWriteBitcodeToMemoryBuffer(mod : ModuleRef) : MemoryBufferRef
fun dispose_memory_buffer = LLVMDisposeMemoryBuffer(buf : MemoryBufferRef) : Void
fun get_buffer_start = LLVMGetBufferStart(buf : MemoryBufferRef) : UInt8*
fun get_buffer_size = LLVMGetBufferSize(buf : MemoryBufferRef) : LibC::SizeT

{% if LibLLVM::IS_36 || LibLLVM::IS_35 %}
fun add_target_data = LLVMAddTargetData(td : TargetDataRef, pm : PassManagerRef)
Loading