implements PR-O2.1
This commit is contained in:
parent
d915e39511
commit
5f78df750d
@ -0,0 +1,91 @@
|
|||||||
|
package p.studio.compiler.backend.bytecode;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public class BytecodeLinkPrecheckService {
|
||||||
|
|
||||||
|
public void validate(final BytecodeModule module) {
|
||||||
|
final var input = module == null ? BytecodeModule.empty() : module;
|
||||||
|
validateFunctionBounds(input);
|
||||||
|
validateExports(input);
|
||||||
|
validateHostcallIndices(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateFunctionBounds(final BytecodeModule module) {
|
||||||
|
var previousEnd = 0;
|
||||||
|
for (final var function : module.functions()) {
|
||||||
|
if (function.codeOffset() < 0 || function.codeLen() < 0) {
|
||||||
|
throw new BytecodeMarshalingException(
|
||||||
|
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_FUNCTION_BOUNDS_INVALID,
|
||||||
|
"negative function code bounds");
|
||||||
|
}
|
||||||
|
if (function.codeOffset() < previousEnd) {
|
||||||
|
throw new BytecodeMarshalingException(
|
||||||
|
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_FUNCTION_BOUNDS_INVALID,
|
||||||
|
"function offsets must be monotonic");
|
||||||
|
}
|
||||||
|
final var end = function.codeOffset() + function.codeLen();
|
||||||
|
if (end < function.codeOffset() || end > module.code().length) {
|
||||||
|
throw new BytecodeMarshalingException(
|
||||||
|
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_FUNCTION_BOUNDS_INVALID,
|
||||||
|
"function code range exceeds module code length");
|
||||||
|
}
|
||||||
|
previousEnd = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateExports(final BytecodeModule module) {
|
||||||
|
for (final var export : module.exports()) {
|
||||||
|
if (export.funcIdx() < 0 || export.funcIdx() >= module.functions().size()) {
|
||||||
|
throw new BytecodeMarshalingException(
|
||||||
|
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_EXPORT_FUNC_INDEX_INVALID,
|
||||||
|
"export references invalid func_idx: " + export.funcIdx());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateHostcallIndices(final BytecodeModule module) {
|
||||||
|
var pc = 0;
|
||||||
|
while (pc < module.code().length) {
|
||||||
|
if (pc + 2 > module.code().length) {
|
||||||
|
throw new BytecodeMarshalingException(
|
||||||
|
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION,
|
||||||
|
"truncated opcode at pc=" + pc);
|
||||||
|
}
|
||||||
|
final var opcode = readU16(module.code(), pc);
|
||||||
|
final var size = sizeForOpcode(opcode);
|
||||||
|
if (pc + size > module.code().length) {
|
||||||
|
throw new BytecodeMarshalingException(
|
||||||
|
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION,
|
||||||
|
"truncated instruction at pc=" + pc);
|
||||||
|
}
|
||||||
|
if (opcode == 0x71) {
|
||||||
|
final var index = readU32(module.code(), pc + 2);
|
||||||
|
if (index < 0 || index >= module.syscalls().size()) {
|
||||||
|
throw new BytecodeMarshalingException(
|
||||||
|
BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_HOSTCALL_INDEX_INVALID,
|
||||||
|
"hostcall index out of bounds: " + index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pc += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int sizeForOpcode(final int opcode) {
|
||||||
|
return switch (opcode) {
|
||||||
|
case 0x50, 0x71, 0x72, 0x02, 0x03, 0x04, 0x10, 0x18, 0x40, 0x41, 0x42, 0x43, 0x56 -> 6;
|
||||||
|
case 0x14, 0x15, 0x52, 0x54 -> 10;
|
||||||
|
case 0x16 -> 3;
|
||||||
|
default -> 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readU16(final byte[] code, final int offset) {
|
||||||
|
return ByteBuffer.wrap(code, offset, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readU32(final byte[] code, final int offset) {
|
||||||
|
return ByteBuffer.wrap(code, offset, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,4 +7,8 @@ public enum BytecodeMarshalingErrorCode {
|
|||||||
MARSHAL_FORMAT_PC_SPAN_OUT_OF_BOUNDS,
|
MARSHAL_FORMAT_PC_SPAN_OUT_OF_BOUNDS,
|
||||||
MARSHAL_LINKAGE_RAW_SYSCALL_IN_PRELOAD,
|
MARSHAL_LINKAGE_RAW_SYSCALL_IN_PRELOAD,
|
||||||
MARSHAL_LINKAGE_HOST_ABI_MISMATCH,
|
MARSHAL_LINKAGE_HOST_ABI_MISMATCH,
|
||||||
|
MARSHAL_VERIFY_PRECHECK_FUNCTION_BOUNDS_INVALID,
|
||||||
|
MARSHAL_VERIFY_PRECHECK_EXPORT_FUNC_INDEX_INVALID,
|
||||||
|
MARSHAL_VERIFY_PRECHECK_HOSTCALL_INDEX_INVALID,
|
||||||
|
MARSHAL_VERIFY_PRECHECK_TRUNCATED_INSTRUCTION,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,8 @@ public class BuilderPipelineService {
|
|||||||
new FrontendPhasePipelineStage(),
|
new FrontendPhasePipelineStage(),
|
||||||
new LowerToIRVMPipelineStage(),
|
new LowerToIRVMPipelineStage(),
|
||||||
new OptimizeIRVMPipelineStage(),
|
new OptimizeIRVMPipelineStage(),
|
||||||
new EmitBytecodePipelineStage()
|
new EmitBytecodePipelineStage(),
|
||||||
|
new LinkBytecodePipelineStage()
|
||||||
);
|
);
|
||||||
INSTANCE = new BuilderPipelineService(stages);
|
INSTANCE = new BuilderPipelineService(stages);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
package p.studio.compiler.workspaces.stages;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodeLinkPrecheckService;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodeMarshalingException;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.models.BuilderPipelineContext;
|
||||||
|
import p.studio.compiler.workspaces.PipelineStage;
|
||||||
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class LinkBytecodePipelineStage implements PipelineStage {
|
||||||
|
private final BytecodeLinkPrecheckService linkPrecheckService;
|
||||||
|
|
||||||
|
public LinkBytecodePipelineStage() {
|
||||||
|
this(new BytecodeLinkPrecheckService());
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkBytecodePipelineStage(final BytecodeLinkPrecheckService linkPrecheckService) {
|
||||||
|
this.linkPrecheckService = linkPrecheckService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BuildingIssueSink run(BuilderPipelineContext ctx, LogAggregator logs) {
|
||||||
|
if (ctx.bytecodeModule == null) {
|
||||||
|
return BuildingIssueSink.empty()
|
||||||
|
.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.phase("BACKEND_LINK_PRECHECK")
|
||||||
|
.code("MARSHAL_FORMAT_STAGE_INPUT_MISSING")
|
||||||
|
.message("[BUILD]: bytecode module is missing before LinkBytecode stage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
linkPrecheckService.validate(ctx.bytecodeModule);
|
||||||
|
} catch (BytecodeMarshalingException e) {
|
||||||
|
return BuildingIssueSink.empty()
|
||||||
|
.report(builder -> builder
|
||||||
|
.error(true)
|
||||||
|
.phase("BACKEND_LINK_PRECHECK")
|
||||||
|
.code(e.code().name())
|
||||||
|
.message("[BUILD]: bytecode link precheck failed: " + e.getMessage())
|
||||||
|
.exception(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
return BuildingIssueSink.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
package p.studio.compiler.backend.bytecode;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class BytecodeLinkPrecheckServiceTest {
|
||||||
|
|
||||||
|
private final BytecodeLinkPrecheckService service = new BytecodeLinkPrecheckService();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateMustAcceptWellFormedModule() {
|
||||||
|
final var module = new BytecodeEmitter().emit(new BytecodeEmitter.EmissionPlan(
|
||||||
|
0,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(new BytecodeModule.Export("main", 0)),
|
||||||
|
ReadOnlyList.from(new BytecodeEmitter.FunctionPlan(
|
||||||
|
"main",
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
ReadOnlyList.from(BytecodeEmitter.Operation.ret())))));
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> service.validate(module));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateMustRejectInvalidExportFunctionIndex() {
|
||||||
|
final var module = new BytecodeModule(
|
||||||
|
0,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 2, 0, 0, 0, 1)),
|
||||||
|
codeRet(),
|
||||||
|
null,
|
||||||
|
ReadOnlyList.from(new BytecodeModule.Export("main", 2)),
|
||||||
|
ReadOnlyList.empty());
|
||||||
|
|
||||||
|
final var thrown = assertThrows(BytecodeMarshalingException.class, () -> service.validate(module));
|
||||||
|
assertEquals(BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_EXPORT_FUNC_INDEX_INVALID, thrown.code());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateMustRejectHostcallIndexOutOfBounds() {
|
||||||
|
final var module = new BytecodeModule(
|
||||||
|
0,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 8, 0, 0, 0, 1)),
|
||||||
|
codeHostcall(3),
|
||||||
|
null,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(new BytecodeModule.SyscallDecl("gfx", "draw_pixel", 1, 0, 0)));
|
||||||
|
|
||||||
|
final var thrown = assertThrows(BytecodeMarshalingException.class, () -> service.validate(module));
|
||||||
|
assertEquals(BytecodeMarshalingErrorCode.MARSHAL_VERIFY_PRECHECK_HOSTCALL_INDEX_INVALID, thrown.code());
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] codeHostcall(final int index) {
|
||||||
|
final var out = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
out.putShort((short) 0x71);
|
||||||
|
out.putInt(index);
|
||||||
|
out.putShort((short) 0x51);
|
||||||
|
return out.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] codeRet() {
|
||||||
|
final var out = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
out.putShort((short) 0x51);
|
||||||
|
return out.array();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package p.studio.compiler.workspaces;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import p.studio.compiler.workspaces.stages.EmitBytecodePipelineStage;
|
import p.studio.compiler.workspaces.stages.EmitBytecodePipelineStage;
|
||||||
import p.studio.compiler.workspaces.stages.FrontendPhasePipelineStage;
|
import p.studio.compiler.workspaces.stages.FrontendPhasePipelineStage;
|
||||||
|
import p.studio.compiler.workspaces.stages.LinkBytecodePipelineStage;
|
||||||
import p.studio.compiler.workspaces.stages.LoadSourcesPipelineStage;
|
import p.studio.compiler.workspaces.stages.LoadSourcesPipelineStage;
|
||||||
import p.studio.compiler.workspaces.stages.LowerToIRVMPipelineStage;
|
import p.studio.compiler.workspaces.stages.LowerToIRVMPipelineStage;
|
||||||
import p.studio.compiler.workspaces.stages.OptimizeIRVMPipelineStage;
|
import p.studio.compiler.workspaces.stages.OptimizeIRVMPipelineStage;
|
||||||
@ -31,8 +32,8 @@ class BuilderPipelineServiceOrderTest {
|
|||||||
FrontendPhasePipelineStage.class,
|
FrontendPhasePipelineStage.class,
|
||||||
LowerToIRVMPipelineStage.class,
|
LowerToIRVMPipelineStage.class,
|
||||||
OptimizeIRVMPipelineStage.class,
|
OptimizeIRVMPipelineStage.class,
|
||||||
EmitBytecodePipelineStage.class),
|
EmitBytecodePipelineStage.class,
|
||||||
|
LinkBytecodePipelineStage.class),
|
||||||
stageTypes);
|
stageTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,58 @@
|
|||||||
|
package p.studio.compiler.workspaces.stages;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.backend.bytecode.BytecodeModule;
|
||||||
|
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||||
|
import p.studio.compiler.models.BuilderPipelineContext;
|
||||||
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class LinkBytecodePipelineStageTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustFailWhenBytecodeModuleIsMissing() {
|
||||||
|
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||||
|
final var stage = new LinkBytecodePipelineStage();
|
||||||
|
|
||||||
|
final var issues = stage.run(ctx, LogAggregator.empty());
|
||||||
|
final var firstIssue = issues.asCollection().iterator().next();
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertEquals("BACKEND_LINK_PRECHECK", firstIssue.getPhase());
|
||||||
|
assertEquals("MARSHAL_FORMAT_STAGE_INPUT_MISSING", firstIssue.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runMustReportHostcallIndexPrecheckFailure() {
|
||||||
|
final var ctx = BuilderPipelineContext.compilerContext(new BuilderPipelineConfig(false, "."));
|
||||||
|
ctx.bytecodeModule = new BytecodeModule(
|
||||||
|
0,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 8, 0, 0, 0, 1)),
|
||||||
|
codeHostcall(1),
|
||||||
|
null,
|
||||||
|
ReadOnlyList.empty(),
|
||||||
|
ReadOnlyList.empty());
|
||||||
|
|
||||||
|
final var stage = new LinkBytecodePipelineStage();
|
||||||
|
final var issues = stage.run(ctx, LogAggregator.empty());
|
||||||
|
final var firstIssue = issues.asCollection().iterator().next();
|
||||||
|
|
||||||
|
assertTrue(issues.hasErrors());
|
||||||
|
assertEquals("MARSHAL_VERIFY_PRECHECK_HOSTCALL_INDEX_INVALID", firstIssue.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] codeHostcall(final int index) {
|
||||||
|
final var out = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
out.putShort((short) 0x71);
|
||||||
|
out.putInt(index);
|
||||||
|
out.putShort((short) 0x51);
|
||||||
|
return out.array();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user