620 lines
34 KiB
Plaintext
620 lines
34 KiB
Plaintext
import "@std/io"
|
|
import "@source/ir"
|
|
import "@source/diagnostics"
|
|
|
|
@hide
|
|
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
|
|
output :: state.output
|
|
type :: Type{ kind = instruction.type, shape = instruction.array_type }
|
|
result := fresh(state)
|
|
match instruction.op {
|
|
.constant: {
|
|
if (instruction.type == .void) return
|
|
try io.write_all(output, " store ")
|
|
try write_type(output, state.module, type)
|
|
try io.write_all(output, " ")
|
|
try write_constant(output, state.module, instruction.constant)
|
|
try io.print(", ptr %v{}\n", output, {u32(instruction.destination)})
|
|
return
|
|
}
|
|
.undefined_value: {
|
|
try io.write_all(output, " store ")
|
|
try write_type(output, state.module, type)
|
|
try io.print(" poison, ptr %v{}\n", output, {u32(instruction.destination)})
|
|
return
|
|
}
|
|
.load_local: {
|
|
try check_local(state, instruction.slot, true, instruction.span)
|
|
try io.print(" %r{} = load ", output, {result})
|
|
try write_type(output, state.module, type)
|
|
try io.print(", ptr %l{}\n", output, {instruction.slot})
|
|
}
|
|
.store_local, .assign_local: {
|
|
if (instruction.op == .assign_local) try check_local(state, instruction.slot, false, instruction.span)
|
|
value :: try load(state, instruction.left)
|
|
try io.write_all(output, " store ")
|
|
try write_type(output, state.module, state.values[usize(instruction.left)])
|
|
try io.print(" %r{}, ptr %l{}\n", output, {value, instruction.slot})
|
|
if (instruction.op == .store_local) try io.print(" store i1 true, ptr %i{}\n", output, {instruction.slot})
|
|
return
|
|
}
|
|
.reset_local: {
|
|
try io.print(" store i1 false, ptr %i{}\n", output, {instruction.slot})
|
|
return
|
|
}
|
|
.load_global: {
|
|
pointer :: fresh(state)
|
|
position :: location(state.line_starts, instruction.span)
|
|
try io.print(" %r{} = call ptr @global{}(i64 {}, i64 {}, i64 {})\n", output, {pointer, instruction.slot, usize(instruction.span.source), position.line, position.column})
|
|
try io.print(" %r{} = load ", output, {result})
|
|
try write_type(output, state.module, type)
|
|
try io.print(", ptr %r{}\n", output, {pointer})
|
|
}
|
|
.array_literal, .record_literal: {
|
|
for (0..instruction.arguments_count) |index| {
|
|
operand :: block.arguments.items[instruction.arguments_start + index]
|
|
value :: try load(state, operand)
|
|
pointer :: fresh(state)
|
|
try io.print(" %r{} = getelementptr ", output, {pointer})
|
|
try write_type(output, state.module, type)
|
|
index_type []u8 :: if (instruction.op == .record_literal) "i32" else "i64"
|
|
try io.print(", ptr %v{}, i64 0, {s} {}\n store ", output, {u32(instruction.destination), index_type, index})
|
|
try write_type(output, state.module, state.values[usize(operand)])
|
|
try io.print(" %r{}, ptr %r{}\n", output, {value, pointer})
|
|
}
|
|
return
|
|
}
|
|
.union_literal: {
|
|
shape :: state.module.arrays.items[usize(instruction.array_type)]
|
|
try io.write_all(output, " store ")
|
|
try write_type(output, state.module, type)
|
|
try io.print(" zeroinitializer, ptr %v{}\n", output, {u32(instruction.destination)})
|
|
if (shape.tag_type != .void) {
|
|
try io.write_all(output, " store ")
|
|
try write_type(output, state.module, Type{ kind = shape.tag_type })
|
|
try io.write_all(output, " ")
|
|
try write_constant(output, state.module, instruction.constant)
|
|
try io.print(", ptr %v{}\n", output, {u32(instruction.destination)})
|
|
}
|
|
if (instruction.left != ir.no_value) {
|
|
payload :: try load(state, instruction.left)
|
|
pointer :: fresh(state)
|
|
try io.print(" %r{} = getelementptr i8, ptr %v{}, i64 {}\n", output, {pointer, u32(instruction.destination), shape.payload_offset})
|
|
try store_payload(state, state.values[usize(instruction.left)], payload, pointer)
|
|
}
|
|
return
|
|
}
|
|
.union_tag: {
|
|
try io.print(" %r{} = load ", output, {result})
|
|
try write_type(output, state.module, type)
|
|
try io.print(", ptr %v{}\n", output, {u32(instruction.left)})
|
|
}
|
|
.field: {
|
|
base_type :: state.values[usize(instruction.left)]
|
|
shape :: state.module.arrays.items[usize(base_type.shape)]
|
|
if (shape.is_union) {
|
|
pointer :: fresh(state)
|
|
try io.print(" %r{} = getelementptr i8, ptr %v{}, i64 0\n", output, {pointer, u32(instruction.left)})
|
|
try check_union(state, pointer, shape, usize(instruction.slot), instruction.span)
|
|
if (instruction.type == .void) return
|
|
payload :: fresh(state)
|
|
try io.print(" %r{} = getelementptr i8, ptr %r{}, i64 {}\n %r{} = load ", output, {payload, pointer, shape.payload_offset, result})
|
|
try write_type(output, state.module, type)
|
|
try io.print(", ptr %r{}\n", output, {payload})
|
|
} else {
|
|
base :: try load(state, instruction.left)
|
|
try io.print(" %r{} = extractvalue ", output, {result})
|
|
try write_type(output, state.module, base_type)
|
|
try io.print(" %r{}, {}\n", output, {base, instruction.slot})
|
|
}
|
|
}
|
|
.field_address: {
|
|
base_type :: state.values[usize(instruction.left)]
|
|
base :: try load(state, instruction.left)
|
|
data :: fresh(state)
|
|
flag :: fresh(state)
|
|
pointer :: fresh(state)
|
|
address :: fresh(state)
|
|
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {data, base})
|
|
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, base})
|
|
shape :: state.module.arrays.items[usize(base_type.shape)]
|
|
if (shape.is_union) {
|
|
if (shape.tag_type != .void) {
|
|
position :: location(state.line_starts, instruction.span)
|
|
try io.print(" call void @check_init(ptr %r{}, i1 true, i64 {}, i64 {}, i64 {})\n", output, {flag, usize(instruction.span.source), position.line, position.column})
|
|
try check_union(state, data, shape, usize(instruction.slot), instruction.span)
|
|
}
|
|
try io.print(" %r{} = getelementptr i8, ptr %r{}, i64 {}\n", output, {pointer, data, shape.payload_offset})
|
|
} else {
|
|
try io.print(" %r{} = getelementptr ", output, {pointer})
|
|
try write_type(output, state.module, Type{ kind = .record, shape = base_type.shape })
|
|
try io.print(", ptr %r{}, i64 0, i32 {}\n", output, {data, instruction.slot})
|
|
}
|
|
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
|
|
try io.print(" %r{} = insertvalue %address %r{}, ptr %r{}, 1\n", output, {result, address, flag})
|
|
}
|
|
.index, .index_address: {
|
|
index :: try load(state, instruction.right)
|
|
base_type :: state.values[usize(instruction.left)]
|
|
shape :: state.module.arrays.items[usize(base_type.shape)]
|
|
failed :: fresh(state)
|
|
try io.print(" %r{} = icmp uge i64 %r{}, {}\n", output, {failed, index, shape.len})
|
|
try check_failure(state, failed, .index_out_of_bounds, instruction.span)
|
|
pointer :: fresh(state)
|
|
if (instruction.op == .index_address) {
|
|
base :: try load(state, instruction.left)
|
|
data :: fresh(state)
|
|
flag :: fresh(state)
|
|
address :: fresh(state)
|
|
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {data, base})
|
|
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, base})
|
|
try io.print(" %r{} = getelementptr ", output, {pointer})
|
|
try write_type(output, state.module, Type{ kind = .array, shape = base_type.shape })
|
|
try io.print(", ptr %r{}, i64 0, i64 %r{}\n", output, {data, index})
|
|
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
|
|
try io.print(" %r{} = insertvalue %address %r{}, ptr %r{}, 1\n", output, {result, address, flag})
|
|
} else {
|
|
try io.print(" %r{} = getelementptr ", output, {pointer})
|
|
try write_type(output, state.module, base_type)
|
|
try io.print(", ptr %v{}, i64 0, i64 %r{}\n %r{} = load ", output, {u32(instruction.left), index, result})
|
|
try write_type(output, state.module, type)
|
|
try io.print(", ptr %r{}\n", output, {pointer})
|
|
}
|
|
}
|
|
.address_local: {
|
|
address :: fresh(state)
|
|
try io.print(" %r{} = insertvalue %address poison, ptr %l{}, 0\n", output, {address, instruction.slot})
|
|
try io.print(" %r{} = insertvalue %address %r{}, ptr %i{}, 1\n", output, {result, address, instruction.slot})
|
|
}
|
|
.address_global: {
|
|
pointer :: fresh(state)
|
|
address :: fresh(state)
|
|
position :: location(state.line_starts, instruction.span)
|
|
try io.print(" %r{} = call ptr @global{}(i64 {}, i64 {}, i64 {})\n", output, {pointer, instruction.slot, usize(instruction.span.source), position.line, position.column})
|
|
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
|
|
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
|
|
}
|
|
.pointer_from_address: {
|
|
address :: try load(state, instruction.left)
|
|
flag :: fresh(state)
|
|
position :: location(state.line_starts, instruction.span)
|
|
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, address})
|
|
try io.print(" call void @check_init(ptr %r{}, i1 true, i64 {}, i64 {}, i64 {})\n", output, {flag, usize(instruction.span.source), position.line, position.column})
|
|
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {result, address})
|
|
}
|
|
.pointer_address: {
|
|
pointer :: try load(state, instruction.left)
|
|
missing :: fresh(state)
|
|
try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {missing, pointer})
|
|
try check_failure(state, missing, .invalid_pointer_access, instruction.span)
|
|
address :: fresh(state)
|
|
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
|
|
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
|
|
}
|
|
.pointer_offset: {
|
|
pointer :: try load(state, instruction.left)
|
|
index :: try load(state, instruction.right)
|
|
try io.print(" %r{} = getelementptr ", output, {result})
|
|
try write_type(output, state.module, Type{ kind = instruction.pointee, shape = instruction.array_type })
|
|
try io.print(", ptr %r{}, i64 %r{}\n", output, {pointer, index})
|
|
}
|
|
.pointer_cast: result = try load(state, instruction.left)
|
|
.string_pointer, .string_length: {
|
|
string :: try load(state, instruction.left)
|
|
field usize :: if (instruction.op == .string_pointer) 0 else 1
|
|
try io.print(" %r{} = extractvalue %string %r{}, {}\n", output, {result, string, field})
|
|
}
|
|
.optional_check: {
|
|
operand :: try load(state, instruction.left)
|
|
failed :: fresh(state)
|
|
if (state.values[usize(instruction.left)].kind == .bool) try io.print(" %r{} = xor i1 %r{}, true\n", output, {failed, operand})
|
|
else try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {failed, operand})
|
|
try check_failure(state, failed, .null_unwrap, instruction.span)
|
|
return
|
|
}
|
|
.slice_check: {
|
|
start :: try load(state, instruction.left)
|
|
end :: try load(state, instruction.right)
|
|
reversed :: fresh(state)
|
|
try io.print(" %r{} = icmp ugt i64 %r{}, %r{}\n", output, {reversed, start, end})
|
|
failed := reversed
|
|
if (instruction.extra != ir.no_value) {
|
|
bound :: try load(state, instruction.extra)
|
|
outside :: fresh(state)
|
|
failed = fresh(state)
|
|
try io.print(" %r{} = icmp ugt i64 %r{}, %r{}\n", output, {outside, end, bound})
|
|
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {failed, reversed, outside})
|
|
}
|
|
try check_failure(state, failed, .invalid_slice_bounds, instruction.span)
|
|
return
|
|
}
|
|
.sentinel_check: {
|
|
pointer :: try load(state, instruction.left)
|
|
expected :: try load(state, instruction.right)
|
|
missing :: fresh(state)
|
|
try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {missing, pointer})
|
|
try check_failure(state, missing, .invalid_pointer_access, instruction.span)
|
|
actual :: fresh(state)
|
|
element :: Type{ kind = instruction.pointee, shape = instruction.array_type }
|
|
try io.print(" %r{} = load ", output, {actual})
|
|
try write_type(output, state.module, element)
|
|
try io.print(", ptr %r{}, align 1\n", output, {pointer})
|
|
failed :: try sentinel_difference(state, element, actual, expected)
|
|
try check_failure(state, failed, .sentinel_mismatch, instruction.span)
|
|
return
|
|
}
|
|
.memory_copy, .memory_set: {
|
|
try emit_memory(state, instruction)
|
|
return
|
|
}
|
|
.load_address, .store_address: {
|
|
address :: try load(state, instruction.left)
|
|
pointer :: fresh(state)
|
|
flag :: fresh(state)
|
|
position :: location(state.line_starts, instruction.span)
|
|
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {pointer, address})
|
|
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, address})
|
|
try io.print(" call void @check_init(ptr %r{}, i1 {}, i64 {}, i64 {}, i64 {})\n", output, {flag, instruction.op == .load_address, usize(instruction.span.source), position.line, position.column})
|
|
if (instruction.op == .store_address) {
|
|
value :: try load(state, instruction.right)
|
|
try store_payload(state, state.values[usize(instruction.right)], value, pointer)
|
|
return
|
|
}
|
|
try io.print(" %r{} = load ", output, {result})
|
|
try write_type(output, state.module, type)
|
|
try io.print(", ptr %r{}, align 1\n", output, {pointer})
|
|
}
|
|
.call: {
|
|
callee :: try load(state, instruction.left)
|
|
# operands are frame snapshots; loading them before the call preserves evaluation order.
|
|
# argument registers are consecutive because load emits exactly one fresh register.
|
|
first_argument :: state.next_register
|
|
for (0..instruction.arguments_count) |index| {
|
|
_ = try load(state, block.arguments.items[instruction.arguments_start + index])
|
|
}
|
|
if (instruction.type != .void) try io.print(" %r{} = call ", output, {result})
|
|
else try io.write_all(output, " call ")
|
|
try write_abi_result(output, state.module, type)
|
|
try io.print(" %r{}(", output, {callee})
|
|
for (0..instruction.arguments_count) |index| {
|
|
if (index != 0) try io.write_all(output, ", ")
|
|
operand :: block.arguments.items[instruction.arguments_start + index]
|
|
try write_abi_parameter(output, state.module, state.values[usize(operand)])
|
|
try io.print(" %r{}", output, {first_argument + index})
|
|
}
|
|
try io.write_all(output, ")\n")
|
|
}
|
|
.convert: result = try emit_conversion(state, instruction)
|
|
.negate: {
|
|
operand :: try load(state, instruction.left)
|
|
if (instruction.type == .float) {
|
|
try io.print(" %r{} = fneg double %r{}\n", output, {result, operand})
|
|
} else {
|
|
pair :: fresh(state)
|
|
overflow :: fresh(state)
|
|
bits :: ir.integer_bits(instruction.type)
|
|
sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s"
|
|
try io.print(" %r{} = call {{ i{}, i1 }} @llvm.{s}sub.with.overflow.i{}(i{} 0, i{} %r{})\n", output, {pair, bits, sign, bits, bits, bits, operand})
|
|
try io.print(" %r{} = extractvalue {{ i{}, i1 }} %r{}, 1\n", output, {overflow, bits, pair})
|
|
try check_failure(state, overflow, .integer_overflow, instruction.span)
|
|
try io.print(" %r{} = extractvalue {{ i{}, i1 }} %r{}, 0\n", output, {result, bits, pair})
|
|
}
|
|
}
|
|
.logical_not: {
|
|
operand :: try load(state, instruction.left)
|
|
try io.print(" %r{} = xor i1 %r{}, true\n", output, {result, operand})
|
|
}
|
|
.add, .subtract, .multiply, .divide: {
|
|
left :: try load(state, instruction.left)
|
|
right :: try load(state, instruction.right)
|
|
if (instruction.type == .float) {
|
|
opcode []u8 :: match instruction.op {
|
|
.add: "fadd"
|
|
.subtract: "fsub"
|
|
.multiply: "fmul"
|
|
.divide: "fdiv"
|
|
else: unreachable
|
|
}
|
|
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
|
|
} else if (instruction.op == .divide) {
|
|
bits :: ir.integer_bits(instruction.type)
|
|
zero :: fresh(state)
|
|
try io.print(" %r{} = icmp eq i{} %r{}, 0\n", output, {zero, bits, right})
|
|
try check_failure(state, zero, .division_by_zero, instruction.span)
|
|
if (!ir.unsigned_integer(instruction.type)) {
|
|
minimum :: fresh(state)
|
|
negative_one :: fresh(state)
|
|
overflow :: fresh(state)
|
|
try io.print(" %r{} = icmp eq i{} %r{}, {}\n", output, {minimum, bits, left, ir.integer_min(instruction.type)})
|
|
try io.print(" %r{} = icmp eq i{} %r{}, -1\n", output, {negative_one, bits, right})
|
|
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {overflow, minimum, negative_one})
|
|
try check_failure(state, overflow, .integer_overflow, instruction.span)
|
|
}
|
|
opcode []u8 :: if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv"
|
|
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, right})
|
|
} else {
|
|
opcode []u8 :: match instruction.op {
|
|
.add: "add"
|
|
.subtract: "sub"
|
|
.multiply: "mul"
|
|
else: unreachable
|
|
}
|
|
bits :: ir.integer_bits(instruction.type)
|
|
sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s"
|
|
pair :: fresh(state)
|
|
overflow :: fresh(state)
|
|
try io.print(" %r{} = call {{ i{}, i1 }} @llvm.{s}{s}.with.overflow.i{}(i{} %r{}, i{} %r{})\n", output, {pair, bits, sign, opcode, bits, bits, left, bits, right})
|
|
try io.print(" %r{} = extractvalue {{ i{}, i1 }} %r{}, 1\n", output, {overflow, bits, pair})
|
|
try check_failure(state, overflow, .integer_overflow, instruction.span)
|
|
try io.print(" %r{} = extractvalue {{ i{}, i1 }} %r{}, 0\n", output, {result, bits, pair})
|
|
}
|
|
}
|
|
.equal, .not_equal, .less, .less_equal, .greater, .greater_equal: {
|
|
left :: try load(state, instruction.left)
|
|
right :: try load(state, instruction.right)
|
|
operand_type :: state.values[usize(instruction.left)]
|
|
if (operand_type.kind == .float) {
|
|
predicate []u8 :: match instruction.op {
|
|
.equal: "oeq"
|
|
.not_equal: "une"
|
|
.less: "olt"
|
|
.less_equal: "ole"
|
|
.greater: "ogt"
|
|
.greater_equal: "oge"
|
|
else: unreachable
|
|
}
|
|
try io.print(" %r{} = fcmp {s} double %r{}, %r{}\n", output, {result, predicate, left, right})
|
|
} else {
|
|
prefix []u8 := ""
|
|
if (instruction.op != .equal and instruction.op != .not_equal) {
|
|
if (ir.unsigned_integer(operand_type.kind)) prefix = "u"
|
|
else prefix = "s"
|
|
}
|
|
predicate []u8 :: match instruction.op {
|
|
.equal: "eq"
|
|
.not_equal: "ne"
|
|
.less: "lt"
|
|
.less_equal: "le"
|
|
.greater: "gt"
|
|
.greater_equal: "ge"
|
|
else: unreachable
|
|
}
|
|
try io.print(" %r{} = icmp {s}{s} ", output, {result, prefix, predicate})
|
|
try write_type(output, state.module, operand_type)
|
|
try io.print(" %r{}, %r{}\n", output, {left, right})
|
|
}
|
|
}
|
|
}
|
|
try store(state, instruction.destination, result)
|
|
}
|
|
|
|
#! compares the complete sentinel value, including nested aggregate fields.
|
|
@hide:file
|
|
sentinel_difference proc(state @mut State, type Type, actual, expected usize) usize ! io.WriteError {
|
|
output :: state.output
|
|
if (ir.aggregate(type.kind)) {
|
|
shape :: state.module.arrays.items[usize(type.shape)]
|
|
count usize :: if (shape.is_union) divtrunc!(shape.byte_size, 8) else if (shape.kind == .record) shape.fields.len else shape.len
|
|
result := fresh(state)
|
|
try io.print(" %r{} = icmp ne i1 false, false\n", output, {result})
|
|
for (0..count) |index| {
|
|
child :: if (shape.is_union) Type{ kind = .u64 } else if (shape.kind == .record) Type{ kind = shape.fields[index].type, shape = shape.fields[index].array_type } else Type{ kind = shape.element, shape = shape.element_array }
|
|
left :: fresh(state)
|
|
right :: fresh(state)
|
|
try io.print(" %r{} = extractvalue ", output, {left})
|
|
try write_type(output, state.module, type)
|
|
try io.print(" %r{}, ", output, {actual})
|
|
if (shape.is_union) try io.write_all(output, "0, ")
|
|
try io.print("{}\n %r{} = extractvalue ", output, {index, right})
|
|
try write_type(output, state.module, type)
|
|
try io.print(" %r{}, ", output, {expected})
|
|
if (shape.is_union) try io.write_all(output, "0, ")
|
|
try io.print("{}\n", output, {index})
|
|
different :: try sentinel_difference(state, child, left, right)
|
|
combined :: fresh(state)
|
|
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {combined, result, different})
|
|
result = combined
|
|
}
|
|
return result
|
|
}
|
|
if (type.kind == .string) {
|
|
left_pointer :: fresh(state)
|
|
right_pointer :: fresh(state)
|
|
left_length :: fresh(state)
|
|
right_length :: fresh(state)
|
|
try io.print(" %r{} = extractvalue %string %r{}, 0\n", output, {left_pointer, actual})
|
|
try io.print(" %r{} = extractvalue %string %r{}, 0\n", output, {right_pointer, expected})
|
|
try io.print(" %r{} = extractvalue %string %r{}, 1\n", output, {left_length, actual})
|
|
try io.print(" %r{} = extractvalue %string %r{}, 1\n", output, {right_length, expected})
|
|
pointer_difference :: try sentinel_difference(state, Type{ kind = .pointer }, left_pointer, right_pointer)
|
|
length_difference :: try sentinel_difference(state, Type{ kind = .usize }, left_length, right_length)
|
|
result :: fresh(state)
|
|
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {result, pointer_difference, length_difference})
|
|
return result
|
|
}
|
|
result :: fresh(state)
|
|
if (type.kind == .void) {
|
|
try io.print(" %r{} = icmp ne i1 false, false\n", output, {result})
|
|
return result
|
|
}
|
|
opcode []u8 :: if (type.kind == .float) "fcmp une" else "icmp ne"
|
|
try io.print(" %r{} = {s} ", output, {result, opcode})
|
|
try write_type(output, state.module, type)
|
|
try io.print(" %r{}, %r{}\n", output, {actual, expected})
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
emit_memory proc(state @mut State, instruction ir.Instruction) void ! io.WriteError {
|
|
output :: state.output
|
|
element :: Type{ kind = instruction.pointee, shape = instruction.array_type }
|
|
width :: ir.storage_size(state.module, element.kind, element.shape)
|
|
if (width == 0) return
|
|
destination :: try load(state, instruction.left)
|
|
operand :: try load(state, instruction.right)
|
|
count :: try load(state, instruction.extra)
|
|
size := count
|
|
if (width != 1) {
|
|
pair :: fresh(state)
|
|
overflow :: fresh(state)
|
|
size = fresh(state)
|
|
try io.print(" %r{} = call {{ i64, i1 }} @llvm.umul.with.overflow.i64(i64 %r{}, i64 {})\n", output, {pair, count, width})
|
|
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 1\n", output, {overflow, pair})
|
|
try check_failure(state, overflow, .integer_overflow, instruction.span)
|
|
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 0\n", output, {size, pair})
|
|
}
|
|
nonempty :: fresh(state)
|
|
destination_null :: fresh(state)
|
|
try io.print(" %r{} = icmp ne i64 %r{}, 0\n", output, {nonempty, count})
|
|
try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {destination_null, destination})
|
|
missing := destination_null
|
|
if (instruction.op == .memory_copy) {
|
|
source_null :: fresh(state)
|
|
missing = fresh(state)
|
|
try io.print(" %r{} = icmp eq ptr %r{}, null\n", output, {source_null, operand})
|
|
try io.print(" %r{} = or i1 %r{}, %r{}\n", output, {missing, destination_null, source_null})
|
|
}
|
|
invalid :: fresh(state)
|
|
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {invalid, nonempty, missing})
|
|
try check_failure(state, invalid, .invalid_pointer_access, instruction.span)
|
|
if (instruction.op == .memory_copy) {
|
|
destination_bits :: fresh(state)
|
|
source_bits :: fresh(state)
|
|
ordered :: fresh(state)
|
|
forward :: fresh(state)
|
|
backward :: fresh(state)
|
|
distance :: fresh(state)
|
|
overlap :: fresh(state)
|
|
try io.print(" %r{} = ptrtoint ptr %r{} to i64\n", output, {destination_bits, destination})
|
|
try io.print(" %r{} = ptrtoint ptr %r{} to i64\n", output, {source_bits, operand})
|
|
try io.print(" %r{} = icmp uge i64 %r{}, %r{}\n", output, {ordered, destination_bits, source_bits})
|
|
try io.print(" %r{} = sub i64 %r{}, %r{}\n", output, {forward, destination_bits, source_bits})
|
|
try io.print(" %r{} = sub i64 %r{}, %r{}\n", output, {backward, source_bits, destination_bits})
|
|
try io.print(" %r{} = select i1 %r{}, i64 %r{}, i64 %r{}\n", output, {distance, ordered, forward, backward})
|
|
try io.print(" %r{} = icmp ult i64 %r{}, %r{}\n", output, {overlap, distance, size})
|
|
try check_failure(state, overlap, .overlapping_memory_copy, instruction.span)
|
|
try io.print(" call void @llvm.memcpy.p0.p0.i64(ptr %r{}, ptr %r{}, i64 %r{}, i1 false)\n", output, {destination, operand, size})
|
|
return
|
|
}
|
|
label :: fresh(state)
|
|
index :: fresh(state)
|
|
next :: fresh(state)
|
|
active :: fresh(state)
|
|
pointer :: fresh(state)
|
|
try io.print(" br label %set_start{}\nset_start{}:\n br label %set_test{}\nset_test{}:\n", output, {label, label, label, label})
|
|
try io.print(" %r{} = phi i64 [ 0, %set_start{} ], [ %r{}, %set_body{} ]\n", output, {index, label, next, label})
|
|
try io.print(" %r{} = icmp ult i64 %r{}, %r{}\n", output, {active, index, count})
|
|
try io.print(" br i1 %r{}, label %set_body{}, label %set_end{}\nset_body{}:\n", output, {active, label, label, label})
|
|
try io.print(" %r{} = getelementptr ", output, {pointer})
|
|
try write_type(output, state.module, element)
|
|
try io.print(", ptr %r{}, i64 %r{}\n", output, {destination, index})
|
|
try store_payload(state, element, operand, pointer)
|
|
try io.print(" %r{} = add i64 %r{}, 1\n br label %set_test{}\nset_end{}:\n", output, {next, index, label, label})
|
|
}
|
|
|
|
@hide:file
|
|
check_union proc(state @mut State, pointer usize, shape ir.ArrayType, field usize, span diagnostics.Span) void ! io.WriteError {
|
|
if (shape.tag_type == .void) return
|
|
tag :: fresh(state)
|
|
failed :: fresh(state)
|
|
try io.print(" %r{} = load ", state.output, {tag})
|
|
try write_type(state.output, state.module, Type{ kind = shape.tag_type })
|
|
try io.print(", ptr %r{}\n %r{} = icmp ne ", state.output, {pointer, failed})
|
|
try write_type(state.output, state.module, Type{ kind = shape.tag_type })
|
|
try io.print(" %r{}, ", state.output, {tag})
|
|
try write_constant(state.output, state.module, shape.fields[field].tag)
|
|
try io.write_all(state.output, "\n")
|
|
try check_failure(state, failed, .inactive_union_field, span)
|
|
}
|
|
|
|
@hide:file
|
|
store_payload proc(state @mut State, type Type, value, pointer usize) void ! io.WriteError {
|
|
if (type.kind == .void) return
|
|
if (ir.aggregate(type.kind)) {
|
|
shape :: state.module.arrays.items[usize(type.shape)]
|
|
if (!shape.is_union) {
|
|
count usize :: if (type.kind == .record) shape.fields.len else shape.len
|
|
for (0..count) |i| {
|
|
child :: if (type.kind == .record) Type{ kind = shape.fields[i].type, shape = shape.fields[i].array_type } else Type{ kind = shape.element, shape = shape.element_array }
|
|
offset usize :: if (type.kind == .record) shape.fields[i].byte_offset else i * ir.storage_size(state.module, shape.element, shape.element_array)
|
|
element :: fresh(state)
|
|
address :: fresh(state)
|
|
try io.print(" %r{} = extractvalue ", state.output, {element})
|
|
try write_type(state.output, state.module, type)
|
|
try io.print(" %r{}, {}\n %r{} = getelementptr i8, ptr %r{}, i64 {}\n", state.output, {value, i, address, pointer, offset})
|
|
try store_payload(state, child, element, address)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
try io.write_all(state.output, " store ")
|
|
try write_type(state.output, state.module, type)
|
|
try io.print(" %r{}, ptr %r{}, align 1\n", state.output, {value, pointer})
|
|
}
|
|
|
|
@hide:file
|
|
emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.WriteError {
|
|
output :: state.output
|
|
source :: state.values[usize(instruction.left)].kind
|
|
destination :: instruction.type
|
|
operand :: try load(state, instruction.left)
|
|
if (source == .float and destination == .float) return operand
|
|
source_bits :: ir.integer_bits(source)
|
|
destination_bits :: ir.integer_bits(destination)
|
|
source_unsigned :: ir.unsigned_integer(source)
|
|
destination_unsigned :: ir.unsigned_integer(destination)
|
|
if (destination == .float) {
|
|
result :: fresh(state)
|
|
opcode []u8 :: if (source_unsigned) "uitofp" else "sitofp"
|
|
try io.print(" %r{} = {s} i{} %r{} to double\n", output, {result, opcode, source_bits, operand})
|
|
return result
|
|
}
|
|
if (source == .float) {
|
|
# compare the truncated value before fptosi/fptoui, whose out-of-range result is poison.
|
|
truncated :: fresh(state)
|
|
lower_valid :: fresh(state)
|
|
upper_valid :: fresh(state)
|
|
valid :: fresh(state)
|
|
failed :: fresh(state)
|
|
upper []u8 :: match destination {
|
|
.i8: "1.280000e+02"
|
|
.i16: "3.276800e+04"
|
|
.i32: "2.147483648e+09"
|
|
.int, .isize: "9.223372036854775808e+18"
|
|
.u8: "2.560000e+02"
|
|
.u16: "6.553600e+04"
|
|
.u32: "4.294967296e+09"
|
|
.u64, .usize: "1.8446744073709551616e+19"
|
|
else: unreachable
|
|
}
|
|
try io.print(" %r{} = call double @llvm.trunc.f64(double %r{})\n", output, {truncated, operand})
|
|
if (destination_unsigned) try io.print(" %r{} = fcmp oge double %r{}, 0.0\n", output, {lower_valid, truncated})
|
|
else try io.print(" %r{} = fcmp oge double %r{}, -{s}\n", output, {lower_valid, truncated, upper})
|
|
try io.print(" %r{} = fcmp olt double %r{}, {s}\n", output, {upper_valid, truncated, upper})
|
|
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {valid, lower_valid, upper_valid})
|
|
try io.print(" %r{} = xor i1 %r{}, true\n", output, {failed, valid})
|
|
try check_failure(state, failed, .integer_overflow, instruction.span)
|
|
result :: fresh(state)
|
|
opcode []u8 :: if (destination_unsigned) "fptoui" else "fptosi"
|
|
try io.print(" %r{} = {s} double %r{} to i{}\n", output, {result, opcode, truncated, destination_bits})
|
|
return result
|
|
}
|
|
if (!source_unsigned and (destination_unsigned or destination_bits < source_bits)) {
|
|
failed :: fresh(state)
|
|
try io.print(" %r{} = icmp slt i{} %r{}, {}\n", output, {failed, source_bits, operand, ir.integer_min(destination)})
|
|
try check_failure(state, failed, .integer_overflow, instruction.span)
|
|
}
|
|
if (destination_bits < source_bits or (source_unsigned and !destination_unsigned and destination_bits == source_bits)) {
|
|
failed :: fresh(state)
|
|
predicate []u8 :: if (source_unsigned) "ugt" else "sgt"
|
|
try io.print(" %r{} = icmp {s} i{} %r{}, {}\n", output, {failed, predicate, source_bits, operand, ir.integer_max(destination)})
|
|
try check_failure(state, failed, .integer_overflow, instruction.span)
|
|
}
|
|
if (source_bits == destination_bits) return operand
|
|
result :: fresh(state)
|
|
opcode []u8 := "trunc"
|
|
if (source_bits < destination_bits) opcode = if (source_unsigned) "zext" else "sext"
|
|
try io.print(" %r{} = {s} i{} %r{} to i{}\n", output, {result, opcode, source_bits, operand, destination_bits})
|
|
return result
|
|
}
|