///|
/// Safe semantic-construction seam used by MilkIR dialect adapters.
///
/// The context exposes only verified operands, explicit environment values,
/// target-neutral native lowering construction, and structured control-flow builders. It
/// does not expose mutable native lowering storage, target instructions, or ABI details.
struct InstructionContext {
builder : @lowering.DirectBuilder
operands : Array[@lowering.Value]
operand_types : Array[@native.ValueType]
result_types : Array[@native.ValueType]
function_result_types : Array[@native.ValueType]
environment : Array[@lowering.Value]
source : @native.SourceLocation?
live_gc_roots : Array[@lowering.Value]
completed : Ref[Bool]
terminates_block : Ref[Bool]
error : Ref[String?]
complete : (Array[@lowering.Value]) -> Unit
}
///|
pub fn InstructionContext::new(
builder : @lowering.DirectBuilder,
operands : Array[@lowering.Value],
operand_types : Array[@native.ValueType],
result_types : Array[@native.ValueType],
function_result_types : Array[@native.ValueType],
environment : Array[@lowering.Value],
source : @native.SourceLocation?,
live_gc_roots : Array[@lowering.Value],
complete : (Array[@lowering.Value]) -> Unit,
) -> InstructionContext {
{
builder,
operands: operands.copy(),
operand_types: operand_types.copy(),
result_types: result_types.copy(),
function_result_types: function_result_types.copy(),
environment: environment.copy(),
source,
live_gc_roots: live_gc_roots.copy(),
completed: Ref(false),
terminates_block: Ref(false),
error: Ref(None),
complete,
}
}
///|
fn InstructionContext::record_error(
self : InstructionContext,
message : String,
) -> Unit {
if self.error.val is None {
self.error.val = Some(message)
}
}
///|
fn[T] InstructionContext::attempt(
self : InstructionContext,
action : () -> T raise,
fallback : T,
) -> T {
action() catch {
error => {
self.record_error(Repr(error).to_string())
fallback
}
}
}
///|
fn InstructionContext::complete_once(
self : InstructionContext,
results : Array[@lowering.Value],
) -> Unit {
if self.completed.val {
self.record_error("dialect instruction completed more than once")
return
}
if results.length() != self.result_types.length() {
self.record_error(
"dialect instruction produced \{results.length()} results, expected \{self.result_types.length()}",
)
return
}
self.completed.val = true
(self.complete)(results)
}
///|
fn InstructionContext::instruction_metadata(
self : InstructionContext,
include_gc_roots : Bool,
) -> @lowering.InstructionMetadata {
@lowering.InstructionMetadata::new(
self.source,
if include_gc_roots {
self.live_gc_roots
} else {
[]
},
)
}
///|
fn InstructionContext::terminator_metadata(
self : InstructionContext,
) -> @lowering.TerminatorMetadata {
@lowering.TerminatorMetadata::new(self.source, self.live_gc_roots)
}
///|
pub fn InstructionContext::operands(
self : InstructionContext,
) -> Array[@lowering.Value] {
self.operands.copy()
}
///|
pub fn InstructionContext::result_types(
self : InstructionContext,
) -> Array[@native.ValueType] {
self.result_types.copy()
}
///|
pub fn InstructionContext::operand_types(
self : InstructionContext,
) -> Array[@native.ValueType] {
self.operand_types.copy()
}
///|
pub fn InstructionContext::function_result_types(
self : InstructionContext,
) -> Array[@native.ValueType] {
self.function_result_types.copy()
}
///|
pub fn InstructionContext::environment(
self : InstructionContext,
) -> Array[@lowering.Value] {
self.environment.copy()
}
///|
pub fn InstructionContext::source(
self : InstructionContext,
) -> @native.SourceLocation? {
self.source
}
///|
pub fn InstructionContext::emit(
self : InstructionContext,
operation : @lowering.Operation,
operands : Array[@lowering.Value],
result_types : Array[@native.ValueType],
) -> Array[@lowering.Value] {
self.attempt(
fn() {
self.builder.emit_with_metadata(
operation,
operands,
result_types,
self.instruction_metadata(false),
)
},
[],
)
}
///|
pub fn InstructionContext::finish_operation(
self : InstructionContext,
operation : @lowering.Operation,
operands : Array[@lowering.Value],
) -> Unit {
let results = self.attempt(
fn() {
self.builder.emit_with_metadata(
operation,
operands,
self.result_types,
self.instruction_metadata(false),
)
},
[],
)
self.complete_once(results)
}
///|
pub fn InstructionContext::emit_call(
self : InstructionContext,
call : @native.NativeCall,
operands : Array[@lowering.Value],
result_types : Array[@native.ValueType],
) -> Array[@lowering.Value] {
self.attempt(
fn() {
self.builder.emit_with_metadata(
Call(call),
operands,
result_types,
self.instruction_metadata(true),
)
},
[],
)
}
///|
pub fn InstructionContext::emit_call_without_roots(
self : InstructionContext,
call : @native.NativeCall,
operands : Array[@lowering.Value],
result_types : Array[@native.ValueType],
) -> Array[@lowering.Value] {
self.attempt(
fn() {
self.builder.emit_with_metadata(
Call(call),
operands,
result_types,
self.instruction_metadata(false),
)
},
[],
)
}
///|
pub fn InstructionContext::finish_call(
self : InstructionContext,
call : @native.NativeCall,
operands : Array[@lowering.Value],
) -> Unit {
let results = self.emit_call(call, operands, self.result_types)
self.complete_once(results)
}
///|
pub fn InstructionContext::finish_call_without_roots(
self : InstructionContext,
call : @native.NativeCall,
operands : Array[@lowering.Value],
) -> Unit {
let results = self.emit_call_without_roots(call, operands, self.result_types)
self.complete_once(results)
}
///|
pub fn InstructionContext::forward_results(
self : InstructionContext,
values : Array[@lowering.Value],
) -> Unit {
if values.length() != self.result_types.length() {
self.record_error(
"dialect instruction forwarded \{values.length()} results, expected \{self.result_types.length()}",
)
return
}
for index, value in values {
if self.builder.value_type(value) != Some(self.result_types[index]) {
self.record_error(
"forwarded dialect result {index} has the wrong semantic type",
)
return
}
}
// The source result is already a value in this construction. Keeping an
// owned Copy instruction only makes mandatory cleanup rediscover the alias.
self.complete_once(values)
}
///|
pub fn InstructionContext::finish_void(self : InstructionContext) -> Unit {
self.complete_once([])
}
///|
pub fn InstructionContext::create_block(
self : InstructionContext,
parameter_types : Array[@native.ValueType],
) -> @lowering.Block {
self.attempt(
fn() { self.builder.create_block(parameter_types) },
({ id: -1, } : @lowering.Block),
)
}
///|
pub fn InstructionContext::create_stack_object(
self : InstructionContext,
size : Int,
alignment : Int,
) -> @native.StackObject {
self.builder.create_stack_object(size, alignment)
}
///|
pub fn InstructionContext::block_parameters(
self : InstructionContext,
block : @lowering.Block,
) -> Array[@lowering.Value] {
self.builder.block_parameters(block)
}
///|
pub fn InstructionContext::switch_to_block(
self : InstructionContext,
block : @lowering.Block,
) -> Unit {
self.attempt(fn() { self.builder.switch_to_block(block) }, ())
}
///|
pub fn InstructionContext::jump(
self : InstructionContext,
target : @lowering.Block,
arguments : Array[@lowering.Value],
) -> Unit {
self.attempt(fn() { self.builder.jump(target, arguments) }, ())
}
///|
pub fn InstructionContext::branch(
self : InstructionContext,
condition : @lowering.Value,
true_target : @lowering.Block,
true_arguments : Array[@lowering.Value],
false_target : @lowering.Block,
false_arguments : Array[@lowering.Value],
) -> Unit {
self.attempt(
fn() {
self.builder.branch(
condition, true_target, true_arguments, false_target, false_arguments,
)
},
(),
)
}
///|
pub fn InstructionContext::trap(
self : InstructionContext,
reason : @native.TrapReason,
) -> Unit {
self.attempt(fn() { self.builder.trap(reason) }, ())
if self.source is Some(source) {
self.builder.set_terminator_source(source)
}
self.terminates_block.val = true
self.finish_void()
}
///|
/// Terminate an adapter-created side block without completing the source
/// dialect instruction. The adapter must switch to a continuation block and
/// complete the instruction there.
pub fn InstructionContext::set_trap_terminator(
self : InstructionContext,
reason : @native.TrapReason,
) -> Unit {
self.attempt(fn() { self.builder.trap(reason) }, ())
if self.source is Some(source) {
self.builder.set_terminator_source(source)
}
}
///|
pub fn InstructionContext::tail_call(
self : InstructionContext,
call : @native.NativeCall,
operands : Array[@lowering.Value],
) -> Unit {
self.attempt(
fn() {
self.builder.tail_call_with_metadata(
call,
operands,
self.terminator_metadata(),
)
},
(),
)
self.terminates_block.val = true
self.finish_void()
}
///|
pub fn InstructionContext::noreturn_call(
self : InstructionContext,
call : @native.NativeCall,
operands : Array[@lowering.Value],
) -> Unit {
let metadata = if call.behavior.gc_safepoint {
self.terminator_metadata()
} else {
@lowering.TerminatorMetadata::new(self.source, [])
}
self.attempt(
fn() { self.builder.noreturn_call_with_metadata(call, operands, metadata) },
(),
)
self.terminates_block.val = true
self.finish_void()
}
///|
pub fn InstructionContext::terminates_block(self : InstructionContext) -> Bool {
self.terminates_block.val
}
///|
pub fn InstructionContext::status(self : InstructionContext) -> String? {
match self.error.val {
Some(message) => Some(message)
None =>
if self.completed.val {
None
} else {
Some("dialect instruction did not complete its result contract")
}
}
}