Skip to content

Commit

Permalink
Support pointer data types for sizeof operator
Browse files Browse the repository at this point in the history
- Introduce test driver for stage 2 compiler tests
- Refactor make rule "check" for checking stage 0 and stage 2
- Replace constant value 4 with PTR_SIZE to give appropriate
  corresponding target arch's pointer size
- Add sizeof tests to both stage 0 and stage 2 tests to validate
  above behavior
- Introduce __SIZE_OF_PTR__ macro in lib/c.c
  • Loading branch information
ChAoSUnItY committed Dec 7, 2024
1 parent b310322 commit 29b6e1a
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 4 deletions.
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,18 @@ $(OUT)/tests/%.elf: tests/%.c $(OUT)/$(STAGE0)
chmod +x $@ ; $(PRINTF) "Running $@ ...\n"
$(Q)$(TARGET_EXEC) $@ && $(call pass)

check: $(TESTBINS) tests/driver.sh
check: check-stage0 check-stage2

check-stage0: $(OUT)/$(STAGE0) $(TESTBINS) tests/driver.sh
$(VECHO) " TEST STAGE 0\n"
tests/driver.sh

check-stage2: $(OUT)/$(STAGE2) $(TESTBINS) tests/driver-stage2.sh
$(VECHO) " TEST STAGE 2\n"
tests/driver-stage2.sh

check-snapshots: $(OUT)/$(STAGE0) $(SNAPSHOTS) tests/check-snapshots.sh
$(VECHO) " TEST SNAPSHOTS\n"
tests/check-snapshots.sh

$(OUT)/%.o: %.c
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,27 @@ Verify that the emitted IRs are identical to the snapshots by specifying `check-
$ make check-snapshots
```

`shecc` comes with unit tests. To run the tests, give `check` as an argument:
`shecc` comes with unit tests consist of stage 0, stage 2. To run these tests, give `check` as an argument:
```shell
$ make check
```

Reference output:
```
TEST STAGE 0
...
int main(int argc, int argv) { exit(sizeof(char)); } => 1
int main(int argc, int argv) { int a; a = 0; switch (3) { case 0: return 2; case 3: a = 10; break; case 1: return 0; } exit(a); } => 10
int main(int argc, int argv) { int a; a = 0; switch (3) { case 0: return 2; default: a = 10; break; } exit(a); } => 10
OK
TEST STAGE 2
...
int main(int argc, int argv) { exit(sizeof(char*)); }
exit code => 4
output =>
int main(int argc, int argv) { exit(sizeof(int*)); }
exit code => 4
output =>
OK
```

Expand Down
2 changes: 2 additions & 0 deletions lib/c.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define false 0

#if defined(__arm__)
#define __SIZEOF_POINTER__ 4
#define __syscall_exit 1
#define __syscall_read 3
#define __syscall_write 4
Expand All @@ -23,6 +24,7 @@
#define __syscall_munmap 91

#elif defined(__riscv)
#define __SIZEOF_POINTER__ 4
#define __syscall_exit 93
#define __syscall_read 63
#define __syscall_write 64
Expand Down
9 changes: 8 additions & 1 deletion src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,11 @@ void read_expr_operand(block_t *parent, basic_block_t **bb)
read_ternary_operation(parent, bb);
lex_expect(T_close_bracket);
} else if (lex_accept(T_sizeof)) {
/* TODO: Use more generalized type grammar parsing function to handle
* type reading
*/
char token[MAX_TYPE_LEN];
int ptr_cnt = 0;

lex_expect(T_open_bracket);
int find_type_flag = lex_accept(T_struct) ? 2 : 1;
Expand All @@ -853,9 +857,12 @@ void read_expr_operand(block_t *parent, basic_block_t **bb)
if (!type)
error("Unable to find type");

while (lex_accept(T_asterisk))
ptr_cnt++;

ph1_ir = add_ph1_ir(OP_load_constant);
vd = require_var(parent);
vd->init_val = type->size;
vd->init_val = ptr_cnt ? PTR_SIZE : type->size;
strcpy(vd->var_name, gen_name());
ph1_ir->dest = vd;
opstack_push(vd);
Expand Down
134 changes: 134 additions & 0 deletions tests/driver-stage2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env bash

set -u

readonly SHECC="$PWD/out/shecc-stage2.elf"

# try - test shecc with given code
# Usage:
# - try exit_code input_code
# compile "input_code" with shecc and expect the compile program exit with
# code "exit_code".
#
# - try exit_code expected_output input_code
# compile "input_code" with shecc and expect the compile program output
# "expected_output" and exit with code "exit_code".
function try() {
local expected="$1"
if [ $# -eq 2 ]; then
local input="$2"
elif [ $# -eq 3 ]; then
local expected_output="$2"
local input="$3"
fi

local tmp_in="$(mktemp --suffix .c)"
local tmp_exe="$(mktemp)"
echo "$input" > "$tmp_in"
"$SHECC" -o "$tmp_exe" "$tmp_in"
chmod +x $tmp_exe

local output=''
output=$($TARGET_EXEC "$tmp_exe")
local actual="$?"

if [ "$actual" != "$expected" ]; then
echo "$input => $expected expected, but got $actual"
echo "input: $tmp_in"
echo "executable: $tmp_exe"
exit 1
elif [ "${expected_output+x}" != "" ] && [ "$output" != "$expected_output" ]; then
echo "$input => $expected_output expected, but got $output"
echo "input: $tmp_in"
echo "executable: $tmp_exe"
exit 2
else
echo "$input"
echo "exit code => $actual"
echo "output => $output"
fi
}

function try_() {
local expected="$1"
local input="$(cat)"
try "$expected" "$input"
}

function try_output() {
local expected="$1"
local expected_output="$2"
local input="$(cat)"
try "$expected" "$expected_output" "$input"
}

# try_compile_error - test shecc with invalid C program
# Usage:
# - try_compile_error invalid_input_code
# compile "invalid_input_code" with shecc so that shecc generates a
# compilation error message.
#
# This function uses shecc to compile invalid code and obtains the exit
# code returned by shecc. The exit code must be a non-zero value to
# indicate that shecc has the ability to parse the invalid code and
# output an error message.
function try_compile_error() {
local input=$(cat)

local tmp_in="$(mktemp --suffix .c)"
local tmp_exe="$(mktemp)"
echo "$input" > "$tmp_in"
"$SHECC" -o "$tmp_exe" "$tmp_in"
local exit_code=$?

if [ 0 == $exit_code ]; then
echo "Error: compilation is passed."
exit 1
fi
}

function items() {
local expected="$1"
local input="$2"
try "$expected" "int main(int argc, int argv) { $input }"
}

function expr() {
local expected="$1"
local input="$2"
items "$expected" "exit($input);"
}

# sizeof
expr 0 "sizeof(void)";
expr 1 "sizeof(_Bool)";
expr 1 "sizeof(char)";
expr 4 "sizeof(int)";
# sizeof pointers
expr 4 "sizeof(void*)";
expr 4 "sizeof(_Bool*)";
expr 4 "sizeof(char*)";
expr 4 "sizeof(int*)";
# sizeof multi-level pointer
expr 4 "sizeof(void**)";
expr 4 "sizeof(_Bool**)";
expr 4 "sizeof(char**)";
expr 4 "sizeof(int**)";
# sizeof struct
try_ 4 << EOF
typedef struct {
int a;
int b;
} struct_t;
int main() { return sizeof(struct_t*); }
EOF
# sizeof enum
try_ 4 << EOF
typedef enum {
A,
B
} enum_t;
int main() { return sizeof(enum_t*); }
EOF

echo OK
30 changes: 29 additions & 1 deletion tests/driver.sh
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,36 @@ items 5 "int a; a = 10; a -= 5; return a;"
items 20 "int *p; int a[3]; a[0] = 10; a[1] = 20; a[2] = 30; p = a; p+=1; return p[0];"

# sizeof
expr 4 "sizeof(int)";
expr 0 "sizeof(void)";
expr 1 "sizeof(_Bool)";
expr 1 "sizeof(char)";
expr 4 "sizeof(int)";
# sizeof pointers
expr 4 "sizeof(void*)";
expr 4 "sizeof(_Bool*)";
expr 4 "sizeof(char*)";
expr 4 "sizeof(int*)";
# sizeof multi-level pointer
expr 4 "sizeof(void**)";
expr 4 "sizeof(_Bool**)";
expr 4 "sizeof(char**)";
expr 4 "sizeof(int**)";
# sizeof struct
try_ 4 << EOF
typedef struct {
int a;
int b;
} struct_t;
int main() { return sizeof(struct_t*); }
EOF
# sizeof enum
try_ 4 << EOF
typedef enum {
A,
B
} enum_t;
int main() { return sizeof(enum_t*); }
EOF

# switch-case
items 10 "int a; a = 0; switch (3) { case 0: return 2; case 3: a = 10; break; case 1: return 0; } return a;"
Expand Down

0 comments on commit 29b6e1a

Please sign in to comment.