add restrict qualifier on pointer arguments

This commit is contained in:
Andrew Kelley 2016-01-08 20:59:47 -07:00
parent d14a31100f
commit 14b9cbd43c
10 changed files with 128 additions and 57 deletions

View File

@ -68,11 +68,11 @@ CompilerFnExpr : token(NumberSign) token(Symbol) token(LParen) Expression token(
CompilerFnType : token(NumberSign) token(Symbol) token(LParen) Type token(RParen)
PointerType : token(Ampersand) option(token(Const)) Type
PointerType : token(Ampersand) option(token(Const)) option(token(Restrict)) Type
MaybeType : token(Question) Type
ArrayType : token(LBracket) option(Expression) token(RBracket) option(token(Const)) Type
ArrayType : token(LBracket) option(Expression) token(RBracket) option(token(Const)) option(token(Restrict)) Type
Block : token(LBrace) list(option(Statement), token(Semicolon)) token(RBrace)

View File

@ -8,7 +8,7 @@ if exists("b:current_syntax")
endif
syn keyword zigOperator as
syn keyword zigStorage const var extern volatile export pub
syn keyword zigStorage const var extern volatile export pub restrict
syn keyword zigStructure struct enum type
syn keyword zigStatement goto break return continue asm
syn keyword zigConditional if else match

View File

@ -135,10 +135,8 @@ static TypeTableEntry *get_number_literal_type_unsigned(CodeGen *g, uint64_t x)
return g->num_lit_types[get_number_literal_kind_unsigned(x)];
}
TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const) {
TypeTableEntry **parent_pointer = is_const ?
&child_type->pointer_const_parent :
&child_type->pointer_mut_parent;
TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const, bool is_restrict) {
TypeTableEntry **parent_pointer = &child_type->pointer_parent[(is_const ? 1 : 0)][(is_restrict ? 1 : 0)];
if (*parent_pointer) {
return *parent_pointer;
} else {
@ -153,6 +151,7 @@ TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool
entry->size_in_bits, entry->align_in_bits, buf_ptr(&entry->name));
entry->data.pointer.child_type = child_type;
entry->data.pointer.is_const = is_const;
entry->data.pointer.is_restrict = is_restrict;
*parent_pointer = entry;
return entry;
@ -241,11 +240,9 @@ static TypeTableEntry *get_array_type(CodeGen *g, ImportTableEntry *import,
}
static TypeTableEntry *get_unknown_size_array_type(CodeGen *g, ImportTableEntry *import,
TypeTableEntry *child_type, bool is_const)
TypeTableEntry *child_type, bool is_const, bool is_restrict)
{
TypeTableEntry **parent_pointer = is_const ?
&child_type->unknown_size_array_const_parent :
&child_type->unknown_size_array_mut_parent;
TypeTableEntry **parent_pointer = &child_type->unknown_size_array_parent[(is_const ? 1 : 0)][(is_restrict ? 1 : 0)];
if (*parent_pointer) {
return *parent_pointer;
} else {
@ -255,7 +252,7 @@ static TypeTableEntry *get_unknown_size_array_type(CodeGen *g, ImportTableEntry
buf_appendf(&entry->name, "[]%s", buf_ptr(&child_type->name));
entry->type_ref = LLVMStructCreateNamed(LLVMGetGlobalContext(), buf_ptr(&entry->name));
TypeTableEntry *pointer_type = get_pointer_to_type(g, child_type, is_const);
TypeTableEntry *pointer_type = get_pointer_to_type(g, child_type, is_const, is_restrict);
unsigned element_count = 2;
LLVMTypeRef element_types[] = {
@ -430,7 +427,9 @@ static TypeTableEntry *eval_const_expr(CodeGen *g, BlockContext *context,
}
}
static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node, ImportTableEntry *import, BlockContext *context) {
static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node, ImportTableEntry *import,
BlockContext *context, bool restrict_allowed)
{
assert(node->type == NodeTypeType);
alloc_codegen_node(node);
TypeNode *type_node = &node->codegen_node->data.type_node;
@ -450,21 +449,47 @@ static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node, ImportTableEntry
}
case AstNodeTypeTypePointer:
{
resolve_type(g, node->data.type.child_type, import, context);
bool use_restrict = false;
if (node->data.type.is_restrict) {
if (!restrict_allowed) {
add_node_error(g, node,
buf_create_from_str("invalid restrict qualifier"));
} else {
use_restrict = true;
}
}
resolve_type(g, node->data.type.child_type, import, context, false);
TypeTableEntry *child_type = node->data.type.child_type->codegen_node->data.type_node.entry;
assert(child_type);
if (child_type->id == TypeTableEntryIdUnreachable) {
add_node_error(g, node,
buf_create_from_str("pointer to unreachable not allowed"));
} else if (child_type->id == TypeTableEntryIdInvalid) {
return child_type;
}
type_node->entry = get_pointer_to_type(g, child_type, node->data.type.is_const);
type_node->entry = g->builtin_types.entry_invalid;
return type_node->entry;
} else if (child_type->id == TypeTableEntryIdInvalid) {
type_node->entry = child_type;
return child_type;
} else {
type_node->entry = get_pointer_to_type(g, child_type, node->data.type.is_const, use_restrict);
return type_node->entry;
}
}
case AstNodeTypeTypeArray:
{
TypeTableEntry *child_type = resolve_type(g, node->data.type.child_type, import, context);
AstNode *size_node = node->data.type.array_size;
bool use_restrict = false;
if (node->data.type.is_restrict) {
if (!restrict_allowed || size_node) {
add_node_error(g, node,
buf_create_from_str("invalid restrict qualifier"));
} else {
use_restrict = true;
}
}
TypeTableEntry *child_type = resolve_type(g, node->data.type.child_type, import, context, false);
if (child_type->id == TypeTableEntryIdUnreachable) {
add_node_error(g, node,
buf_create_from_str("array of unreachable not allowed"));
@ -472,8 +497,6 @@ static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node, ImportTableEntry
return type_node->entry;
}
AstNode *size_node = node->data.type.array_size;
if (size_node) {
TypeTableEntry *size_type = analyze_expression(g, import, context,
g->builtin_types.entry_usize, size_node);
@ -501,14 +524,14 @@ static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node, ImportTableEntry
return type_node->entry;
} else {
type_node->entry = get_unknown_size_array_type(g, import, child_type,
node->data.type.is_const);
node->data.type.is_const, use_restrict);
return type_node->entry;
}
}
case AstNodeTypeTypeMaybe:
{
resolve_type(g, node->data.type.child_type, import, context);
resolve_type(g, node->data.type.child_type, import, context, false);
TypeTableEntry *child_type = node->data.type.child_type->codegen_node->data.type_node.entry;
assert(child_type);
if (child_type->id == TypeTableEntryIdUnreachable) {
@ -571,7 +594,8 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
AstNode *child = node->data.fn_proto.params.at(i);
assert(child->type == NodeTypeParamDecl);
TypeTableEntry *type_entry = resolve_type(g, child->data.param_decl.type, import, import->block_context);
TypeTableEntry *type_entry = resolve_type(g, child->data.param_decl.type,
import, import->block_context, true);
if (type_entry->id == TypeTableEntryIdUnreachable) {
add_node_error(g, child->data.param_decl.type,
buf_sprintf("parameter of type 'unreachable' not allowed"));
@ -583,7 +607,7 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
}
}
resolve_type(g, node->data.fn_proto.return_type, import, import->block_context);
resolve_type(g, node->data.fn_proto.return_type, import, import->block_context, true);
}
static void preview_function_labels(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry) {
@ -644,7 +668,8 @@ static void resolve_struct_type(CodeGen *g, ImportTableEntry *import, TypeTableE
AstNode *field_node = decl_node->data.struct_decl.fields.at(i);
TypeStructField *type_struct_field = &struct_type->data.structure.fields[i];
type_struct_field->name = &field_node->data.struct_field.name;
type_struct_field->type_entry = resolve_type(g, field_node->data.struct_field.type, import, import->block_context);
type_struct_field->type_entry = resolve_type(g, field_node->data.struct_field.type,
import, import->block_context, false);
if (type_struct_field->type_entry->id == TypeTableEntryIdStruct) {
resolve_struct_type(g, import, type_struct_field->type_entry);
@ -1196,15 +1221,18 @@ static TypeTableEntry *resolve_type_compatibility(CodeGen *g, BlockContext *cont
return expected_type;
}
// implicit non-const to const
// implicit non-const to const and ignore restrict
if (expected_type->id == TypeTableEntryIdPointer &&
actual_type->id == TypeTableEntryIdPointer &&
expected_type->data.pointer.is_const &&
!actual_type->data.pointer.is_const)
(!actual_type->data.pointer.is_const || expected_type->data.pointer.is_const))
{
return resolve_type_compatibility(g, context, node,
TypeTableEntry *resolved_type = resolve_type_compatibility(g, context, node,
expected_type->data.pointer.child_type,
actual_type->data.pointer.child_type);
if (resolved_type->id == TypeTableEntryIdInvalid) {
return resolved_type;
}
return expected_type;
}
add_node_error(g, first_executing_node(node),
@ -1335,7 +1363,7 @@ static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *i
return_type = g->builtin_types.entry_usize;
} else if (buf_eql_str(name, "ptr")) {
// TODO determine whether the pointer should be const
return_type = get_pointer_to_type(g, struct_type->data.array.child_type, false);
return_type = get_pointer_to_type(g, struct_type->data.array.child_type, false, false);
} else {
add_node_error(g, node,
buf_sprintf("no member named '%s' in '%s'", buf_ptr(name),
@ -1365,16 +1393,16 @@ static TypeTableEntry *analyze_slice_expr(CodeGen *g, ImportTableEntry *import,
return_type = g->builtin_types.entry_invalid;
} else if (array_type->id == TypeTableEntryIdArray) {
return_type = get_unknown_size_array_type(g, import, array_type->data.array.child_type,
node->data.slice_expr.is_const);
node->data.slice_expr.is_const, false);
} else if (array_type->id == TypeTableEntryIdPointer) {
return_type = get_unknown_size_array_type(g, import, array_type->data.pointer.child_type,
node->data.slice_expr.is_const);
node->data.slice_expr.is_const, false);
} else if (array_type->id == TypeTableEntryIdStruct &&
array_type->data.structure.is_unknown_size_array)
{
return_type = get_unknown_size_array_type(g, import,
array_type->data.structure.fields[0].type_entry->data.pointer.child_type,
node->data.slice_expr.is_const);
node->data.slice_expr.is_const, false);
} else {
add_node_error(g, node,
buf_sprintf("slice of non-array type '%s'", buf_ptr(&array_type->name)));
@ -1490,7 +1518,7 @@ static bool is_op_allowed(TypeTableEntry *type, BinOpType op) {
static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
TypeTableEntry *expected_type, AstNode *node)
{
TypeTableEntry *wanted_type = resolve_type(g, node->data.cast_expr.type, import, context);
TypeTableEntry *wanted_type = resolve_type(g, node->data.cast_expr.type, import, context, false);
TypeTableEntry *actual_type = analyze_expression(g, import, context, nullptr, node->data.cast_expr.expr);
if (wanted_type->id == TypeTableEntryIdInvalid ||
@ -1713,7 +1741,7 @@ static VariableTableEntry *analyze_variable_declaration_raw(CodeGen *g, ImportTa
{
TypeTableEntry *explicit_type = nullptr;
if (variable_declaration->type != nullptr) {
explicit_type = resolve_type(g, variable_declaration->type, import, context);
explicit_type = resolve_type(g, variable_declaration->type, import, context, false);
if (explicit_type->id == TypeTableEntryIdUnreachable) {
add_node_error(g, variable_declaration->type,
buf_sprintf("variable of type 'unreachable' not allowed"));
@ -1837,7 +1865,7 @@ static TypeTableEntry *analyze_struct_val_expr(CodeGen *g, ImportTableEntry *imp
AstNodeStructValueExpr *struct_val_expr = &node->data.struct_val_expr;
TypeTableEntry *type_entry = resolve_type(g, struct_val_expr->type, import, context);
TypeTableEntry *type_entry = resolve_type(g, struct_val_expr->type, import, context, false);
if (type_entry->id == TypeTableEntryIdInvalid) {
return g->builtin_types.entry_invalid;
@ -2017,7 +2045,7 @@ static TypeTableEntry *analyze_compiler_fn_type(CodeGen *g, ImportTableEntry *im
assert(node->type == NodeTypeCompilerFnType);
Buf *name = &node->data.compiler_fn_type.name;
TypeTableEntry *type_entry = resolve_type(g, node->data.compiler_fn_type.type, import, context);
TypeTableEntry *type_entry = resolve_type(g, node->data.compiler_fn_type.type, import, context, false);
if (buf_eql_str(name, "sizeof")) {
uint64_t size_in_bytes = type_entry->size_in_bits / 8;
@ -2221,7 +2249,7 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
if (asm_output->return_type) {
node->data.asm_expr.return_count += 1;
return_type = resolve_type(g, asm_output->return_type, import, context);
return_type = resolve_type(g, asm_output->return_type, import, context, false);
if (node->data.asm_expr.return_count > 1) {
add_node_error(g, node,
buf_sprintf("inline assembly allows up to one output value"));
@ -2357,7 +2385,7 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
break;
}
return_type = get_pointer_to_type(g, child_type, is_const);
return_type = get_pointer_to_type(g, child_type, is_const, false);
break;
}
case PrefixOpDereference:

View File

@ -23,6 +23,7 @@ struct StructValExprNode;
struct TypeTableEntryPointer {
TypeTableEntry *child_type;
bool is_const;
bool is_restrict;
};
struct TypeTableEntryInt {
@ -97,12 +98,10 @@ struct TypeTableEntry {
} data;
// use these fields to make sure we don't duplicate type table entries for the same type
TypeTableEntry *pointer_const_parent;
TypeTableEntry *pointer_mut_parent;
TypeTableEntry *pointer_parent[2][2]; // 0 - const. 1 - restrict
TypeTableEntry *unknown_size_array_parent[2][2]; // 0 - const. 1 - restrict
HashMap<uint64_t, TypeTableEntry *, uint64_hash, uint64_eq> arrays_by_size;
TypeTableEntry *maybe_parent;
TypeTableEntry *unknown_size_array_const_parent;
TypeTableEntry *unknown_size_array_mut_parent;
};
@ -379,7 +378,7 @@ void semantic_analyze(CodeGen *g);
void add_node_error(CodeGen *g, AstNode *node, Buf *msg);
void alloc_codegen_node(AstNode *node);
TypeTableEntry *new_type_table_entry(TypeTableEntryId id);
TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const);
TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const, bool is_restrict);
VariableTableEntry *find_variable(BlockContext *context, Buf *name);
BlockContext *new_block_context(AstNode *node, BlockContext *parent);

View File

@ -77,13 +77,13 @@ static TypeTableEntry *get_type_for_type_node(CodeGen *g, AstNode *type_node) {
return type_node->codegen_node->data.type_node.entry;
}
static LLVMTypeRef fn_proto_type_from_type_node(CodeGen *g, AstNode *type_node) {
static TypeTableEntry *fn_proto_type_from_type_node(CodeGen *g, AstNode *type_node) {
TypeTableEntry *type_entry = get_type_for_type_node(g, type_node);
if (type_entry->id == TypeTableEntryIdStruct || type_entry->id == TypeTableEntryIdArray) {
return get_pointer_to_type(g, type_entry, true)->type_ref;
return get_pointer_to_type(g, type_entry, true, true);
} else {
return type_entry->type_ref;
return type_entry;
}
}
@ -1763,7 +1763,7 @@ static void do_code_gen(CodeGen *g) {
if (is_param_decl_type_void(g, param_node))
continue;
AstNode *type_node = param_node->data.param_decl.type;
param_types[gen_param_index] = fn_proto_type_from_type_node(g, type_node);
param_types[gen_param_index] = fn_proto_type_from_type_node(g, type_node)->type_ref;
gen_param_index += 1;
}
LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, param_count, fn_proto->is_var_args);
@ -1785,6 +1785,28 @@ static void do_code_gen(CodeGen *g) {
LLVMAddFunctionAttr(fn, LLVMNoUnwindAttribute);
}
// set parameter attributes
gen_param_index = 0;
for (int param_decl_i = 0; param_decl_i < fn_proto->params.length; param_decl_i += 1) {
AstNode *param_node = fn_proto->params.at(param_decl_i);
assert(param_node->type == NodeTypeParamDecl);
if (is_param_decl_type_void(g, param_node))
continue;
AstNode *type_node = param_node->data.param_decl.type;
TypeTableEntry *param_type = fn_proto_type_from_type_node(g, type_node);
LLVMValueRef argument_val = LLVMGetParam(fn, gen_param_index);
if (param_type->id == TypeTableEntryIdPointer &&
param_type->data.pointer.is_restrict)
{
LLVMAddAttribute(argument_val, LLVMNoAliasAttribute);
} else if (param_type->id == TypeTableEntryIdPointer &&
param_type->data.pointer.is_const)
{
LLVMAddAttribute(argument_val, LLVMReadOnlyAttribute);
}
gen_param_index += 1;
}
fn_table_entry->fn_value = fn;
}
@ -2032,7 +2054,7 @@ static void define_builtin_types(CodeGen *g) {
LLVMZigEncoding_DW_ATE_unsigned());
g->builtin_types.entry_u64 = entry;
}
g->builtin_types.entry_c_string_literal = get_pointer_to_type(g, g->builtin_types.entry_u8, true);
g->builtin_types.entry_c_string_literal = get_pointer_to_type(g, g->builtin_types.entry_u8, true, false);
{
TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt);
entry->type_ref = LLVMInt8Type();

View File

@ -224,16 +224,18 @@ void ast_print(AstNode *node, int indent) {
}
case AstNodeTypeTypePointer:
{
const char *const_or_mut_str = node->data.type.is_const ? "const" : "var";
fprintf(stderr, "'%s' PointerType\n", const_or_mut_str);
const char *const_or_mut_str = node->data.type.is_const ? "const " : "";
const char *restrict_or_not_str = node->data.type.is_restrict ? "restrict " : "";
fprintf(stderr, "%s%s PointerType\n", const_or_mut_str, restrict_or_not_str);
ast_print(node->data.type.child_type, indent + 2);
break;
}
case AstNodeTypeTypeArray:
{
const char *const_or_mut_str = node->data.type.is_const ? "const" : "var";
fprintf(stderr, "'%s' ArrayType\n", const_or_mut_str);
const char *const_or_mut_str = node->data.type.is_const ? "const " : "";
const char *restrict_or_not_str = node->data.type.is_restrict ? "restrict " : "";
fprintf(stderr, "%s%s ArrayType\n", const_or_mut_str, restrict_or_not_str);
if (node->data.type.array_size)
ast_print(node->data.type.array_size, indent + 2);
ast_print(node->data.type.child_type, indent + 2);
@ -1022,6 +1024,13 @@ static void ast_parse_type_assume_amp(ParseContext *pc, int *token_index, AstNod
node->data.type.is_const = true;
*token_index += 1;
first_type_token = &pc->tokens->at(*token_index);
if (first_type_token->id == TokenIdKeywordRestrict) {
node->data.type.is_restrict = true;
*token_index += 1;
}
} else if (first_type_token->id == TokenIdKeywordRestrict) {
node->data.type.is_restrict = true;
*token_index += 1;
}
node->data.type.child_type = ast_parse_type(pc, token_index);
@ -1079,8 +1088,8 @@ static AstNode *ast_parse_compiler_fn_call(ParseContext *pc, int *token_index, b
/*
Type : token(Symbol) | token(Unreachable) | token(Void) | PointerType | ArrayType | MaybeType | CompilerFnExpr
PointerType : token(Ampersand) option(token(Const)) Type
ArrayType : token(LBracket) option(Expression) token(RBracket) Type
PointerType : token(Ampersand) option(token(Const)) option(token(Restrict)) Type
ArrayType : token(LBracket) option(Expression) token(RBracket) option(token(Const)) option(token(Restrict)) Type
*/
static AstNode *ast_parse_type(ParseContext *pc, int *token_index) {
Token *token = &pc->tokens->at(*token_index);
@ -1129,6 +1138,15 @@ static AstNode *ast_parse_type(ParseContext *pc, int *token_index) {
if (const_tok->id == TokenIdKeywordConst) {
*token_index += 1;
node->data.type.is_const = true;
Token *next_tok = &pc->tokens->at(*token_index);
if (next_tok->id == TokenIdKeywordRestrict) {
*token_index += 1;
node->data.type.is_restrict = true;
}
} else if (const_tok->id == TokenIdKeywordRestrict) {
*token_index += 1;
node->data.type.is_restrict = true;
}
node->data.type.child_type = ast_parse_type(pc, token_index);
@ -1476,7 +1494,7 @@ static PrefixOp tok_to_prefix_op(Token *token) {
}
/*
PrefixOp : token(Not) | token(Dash) | token(Tilde) | (token(Ampersand) option(token(Const)))
PrefixOp : token(Not) | token(Dash) | token(Tilde) | token(Star) | (token(Ampersand) option(token(Const)))
*/
static PrefixOp ast_parse_prefix_op(ParseContext *pc, int *token_index, bool mandatory) {
Token *token = &pc->tokens->at(*token_index);

View File

@ -110,6 +110,7 @@ struct AstNodeType {
AstNode *child_type;
AstNode *array_size; // can be null
bool is_const;
bool is_restrict;
AstNode *compiler_expr;
};

View File

@ -243,6 +243,8 @@ static void end_token(Tokenize *t) {
t->cur_tok->id = TokenIdKeywordBreak;
} else if (mem_eql_str(token_mem, token_len, "null")) {
t->cur_tok->id = TokenIdKeywordNull;
} else if (mem_eql_str(token_mem, token_len, "restrict")) {
t->cur_tok->id = TokenIdKeywordRestrict;
}
t->cur_tok = nullptr;
@ -1019,6 +1021,7 @@ static const char * token_name(Token *token) {
case TokenIdKeywordContinue: return "Continue";
case TokenIdKeywordBreak: return "Break";
case TokenIdKeywordNull: return "Null";
case TokenIdKeywordRestrict: return "Restrict";
case TokenIdLParen: return "LParen";
case TokenIdRParen: return "RParen";
case TokenIdComma: return "Comma";

View File

@ -36,6 +36,7 @@ enum TokenId {
TokenIdKeywordContinue,
TokenIdKeywordBreak,
TokenIdKeywordNull,
TokenIdKeywordRestrict,
TokenIdLParen,
TokenIdRParen,
TokenIdComma,

View File

@ -10,8 +10,7 @@ export fn memset(dest: &u8, c: u8, n: usize) -> &u8 {
return dest;
}
// TODO annotate parameters with noalias
export fn memcpy(dest: &u8, src: &const u8, n: usize) -> &u8 {
export fn memcpy(dest: &restrict u8, src: &const restrict u8, n: usize) -> &u8 {
var index : #typeof(n) = 0;
while (index != n) {
dest[index] = src[index];