Reduce special cases involving the void type
Created by: wks
The current status
void type is a special type in the Mu type system. It has no value, and thus many instructions/mechanisms have special cases for the
Instructions that have special cases for
voidhas no value (In fact it does. The return value of the
BRANCHinstruction, for example, is a value of the
voidtype.), we needed a special syntax to return
void, thus we have
The "new-stack clause" of the
PASS_VALUE <T> %valand
PASS_VOID: for the same reason why we have
The trap handler has a special case for
SWAPSTACK, the trap handler may rebind the thread to a stack and either "pass a value" or "pass
void" or "throw an exception".
Other existing uses
Instructions that always return
FENCE, some common instructions:
@uvm.meta.set_trap_handler: These instructions do not return meaningful values.
Instructions that may return
SWAP_STACK: The callee, client, swappee, or whatever the other end of communication is, may not return meaningful values.
Current properties of
void can only be used in 3 cases:
- As the type of allocation units that do not represent values. Hence it is usable as the referent type of reference types and pointer types. e.g. You can run
NEW <@void>. Each time you NEW a void, you have a new empty object, not the same as any other.
- As the fixed part of a hybrid to indicate the absence of the fixed part. e.g.
hybrid<void int<64>>is a variable-length array of
int<64>, without a fixed part.
- As the type of instructions or the return type of functions that do not return values. e.g. the
voidhas no value (in fact it does, as mentioned before)
voidis neither a scalar type nor a composite type.
- Only scalar types can be used for memory access:
- Only composite types have other types as components: fields/elements
voidis nether storable nor loadable. It does not contain other parts. It cannot be part of a struct/array/vector. i.e. there is no "array of void". The "fixed part of a hybrid" is an exception.
- Only scalar types can be used for memory access:
voidis native-safe: It can be returned from native functions; and there can be
void: Instead of "having no value",
void now has exactly one value: NULL. This is consistent with Python:
NoneType has only one value
void constant: We reuse the
NULL literal to create a "void constant":
.const @VOID <@void> = NULL // The only possible value of void. // For the sake of consistency, we require the client to define it. // // Alternative: make it a pre-defined value, such as the @uvm.predef.void_t type // and the @uvm.predef.VOID value. We could define @uvm.predef.i8, @uvm.predef.i16, // @uvm.predef.i32, @uvm.predef.i64, @uvm.predef.float, @uvm.predef.double, // @uvm.predef.ref_void, @uvm.predef.ref_i32..., @uvm.predef.but the choice seems too arbitrary.
All existing instructions that return
void return this
NULL value. In theory, the following snippet is valid, but stupid:
%entry: %x = BRANCH %bb1 %bb1: RET <@void> %x // return void. Should have said RET <@void> @VOID // or even "RET @VOID" omitting the type argument, because RET always returns the // return type of the current function. ADD, SUB, MUL ... would have to infer the operand // types if the operand type is not provided, but RET does not need to be inferred: the // function return type is explicit.
RETVOID instruction: Use
RET <@void> @VOID instead, or simply
PASS_VAL <@void> @VOID instead. Unlike
RET, the type parameter here is necessary: the type that the swappee expects is dynamic. It may expect a different type at a different
SWAPSTACK site. Guessing the wrong type while swapping has undefined behaviour.
Trap handlers no longer needs a PASS_VOID return case: Instead, pass a
New ways to use
In addition to the existing three ways, i.e. empty objects, hybrid fixed part, empty return value,
void can now be used in the following ways:
RETto return from a function of
SWAPSTACKto swap to a stack that does not expect to receive a value (it receives the
NULLvalue of the
- In the trap handler, rebind the stack which expect void.
They all fit into the category that "the other end of communication" does not pass a value.
Things that should still be forbidden
void must not be a parameter type: I don't have a very compelling reason, but it is completely useless (only increases the apparent arity of a function).
void must not be part of a struct/array/vector or the variable part of a hybrid: Not allowing this will gain us a very nice property: each field/element in any struct/array/vector/varpart has a different offset. In
struct<@i32 void void void void @x>, since
void should have size 0 and alignment 1 (in the sense
void can be allocated at any address a such that a % 1 == 0), void does occupy space. Then all of the void fields are at the same offset as
@x. Another reason: C does not allow void to be a struct field.
Empty structs (
struct<>) should be forbidden: For the same reason as
void as a field. Just use
void because it is so special. C forbids empty structs, too, but GCC allows it.
How about LLVM?
LLVM IR has two syntax for the
ret <type> <value>for example:
ret i32 100
ret voidthis returns void.
LLVM does not have "void constant", either, since
void is not a "first class type".
void is not a "first class type". Only
void and function types are not "first class type". LLVM has both "function" types and "pointer to function" types.
LLVM LangRef does not say parameter types cannot be
void is never used as parameter types. In C,
void is an incomplete type, and thus cannot be a parameter type.